diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ee20f27..04b5fea 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -146,6 +146,7 @@ Push your branch:
### Code Quality Tools
We use ESLint for linting and Prettier for formatting. Please run these before submitting a PR:
+
- `npm run lint` — Check for code quality and style issues.
- `npm run format` — Automatically format your code to project standards.
- `npm run format:check` — Verify that files are correctly formatted.
diff --git a/README.md b/README.md
index 34933c9..2851480 100644
--- a/README.md
+++ b/README.md
@@ -66,14 +66,13 @@ Lightweight social sharing component for web applications. Zero dependencies, fr
[](https://www.npmjs.com/package/social-share-button-aossie)
[](LICENSE)
-
---
## Features
- 🌐 Multiple platforms: WhatsApp, Facebook, X, LinkedIn, Telegram, Reddit, Email, Pinterest
- 🎯 Zero dependencies - pure vanilla JavaScript
-- ⚛️ Framework support: React, Preact, Next.js, Qwik, Vue, Angular, or plain HTML
+- ⚛️ Framework support: React, Preact, Next.js, Nuxt.js, Qwik, Vue, Angular, WordPress, Hugo, Jekyll or plain HTML
- 🔄 Auto-detects current URL and page title
- 📱 Fully responsive and mobile-ready
- 🎨 Customizable themes (dark/light)
@@ -104,11 +103,11 @@ Lightweight social sharing component for web applications. Zero dependencies, fr
No matter which framework you use, integration always follows the same 3 steps:
-| Step | What to do | Where |
-| -------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- |
-| **1️⃣ Load Library** | Add CSS + JS (CDN links) | Global layout file — `index.html` / `layout.tsx` / `_document.tsx` |
-| **2️⃣ Add Container** | Place `
` | The UI component where you want the button to appear |
-| **3️⃣ Initialize** | Call `new SocialShareButton({ container: "#share-button" })` | Inside that component, after the DOM is ready (e.g. `useEffect`, `mounted`, `ngAfterViewInit`) |
+| Step | What to do | Where |
+| -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------ |
+| **1️⃣ Load Library** | Add CSS + JS (CDN links) | Global layout file — `index.html` / `layout.tsx` / `_document.tsx` / `functions.php` |
+| **2️⃣ Add Container** | Place `` | The UI component or WordPress `wp_footer` hook |
+| **3️⃣ Initialize** | Call `new SocialShareButton({ container: "#share-button" })` | Inside that component or WordPress `wp_footer` hook |
> 💡 Pick your framework below for the full copy-paste snippet:
@@ -438,12 +437,12 @@ new window.SocialShareButton({
-
+
```
@@ -487,6 +486,161 @@ export default function Header() {
+
+🔷 WordPress
+
+### Step 1: Enqueue in `functions.php`
+
+Add the following to your theme's `functions.php` to load the library directly from this repository via jsDelivr CDN:
+
+> **Note:** This package is not published to npm. Use the jsDelivr + GitHub CDN link below to load the correct distributable from the [AOSSIE-Org/SocialShareButton](https://github.com/AOSSIE-Org/SocialShareButton) repository.
+
+```php
+function enqueue_social_share_button() {
+ wp_enqueue_style(
+ 'social-share-button',
+ 'https://cdn.jsdelivr.net/gh/AOSSIE-Org/SocialShareButton@v1.0.3/src/social-share-button.css'
+ );
+ wp_enqueue_script(
+ 'social-share-button',
+ 'https://cdn.jsdelivr.net/gh/AOSSIE-Org/SocialShareButton@v1.0.3/src/social-share-button.js',
+ [],
+ null,
+ true // Load in footer
+ );
+}
+add_action('wp_enqueue_scripts', 'enqueue_social_share_button');
+```
+
+### Step 2: Initialize in `functions.php` (Footer Hook)
+
+Use the `wp_footer` hook with a **priority of 21** to inject the container and initialization script. The priority must be higher than the default (10) so WordPress prints the enqueued footer scripts _before_ this function runs:
+
+```php
+function init_social_share_button() { ?>
+
+
+
+
+
+💚 Nuxt.js
+
+### Step 1: Add CDN to your layout file (e.g., `app.vue` or `layouts/default.vue`)
+
+```html
+
+
+
+
+
+
+```
+
+### Step 2: Obtain the Nuxt wrapper component
+
+Currently, the wrapper is not available via CDN and must be added manually. Copy the `src/social-share-button-nuxt.vue` file from this repository into your Nuxt project's `components/` folder. Rename it to `SocialShareButton.vue` to match the usage below.
+
+### Step 3: Use the component in your page or component
+
+Open an **existing** page — typically `pages/index.vue`. Since the component is in the `components/` folder, Nuxt 3 will auto-import it.
+
+```vue
+
+
+
+
+
+```
+
+
+
+
+📄 Hugo / Jekyll
+
+### Step 1: Add CDN to your base layout
+
+Hugo: `layouts/_default/baseof.html`
+Jekyll: `_layouts/default.html`
+
+```html
+
+
+
+
+
+
+
+```
+
+### Step 2: Add the container and initialize
+
+Place this script where you want the button to appear (e.g. at the bottom of a post template).
+
+**Hugo (Post Template):**
+
+```html
+
+
+```
+
+**Jekyll (Post Template):**
+
+```html
+
+
+```
+
+
+
---
## Configuration
diff --git a/docs/Roadmap.md b/docs/Roadmap.md
index 98ae879..bee023d 100644
--- a/docs/Roadmap.md
+++ b/docs/Roadmap.md
@@ -1,7 +1,7 @@
# 🚀 SocialShareButton — Project Roadmap
> **Version:** 2.0 Draft
-> **Status:** Living Document
+> **Status:** Living Document
---
@@ -27,23 +27,23 @@ This section is the ground truth before any planning.
### ✅ What Already Exists
-| Feature | Status | Notes |
-|---|---|---|
-| CDN distribution (jsDelivr) | ✅ | `v1.0.3` |
-| npm package | ✅ | Published as `social-share-button-aossie` (unscoped) |
-| 7 share platforms | ✅ | WhatsApp, Facebook, X, LinkedIn, Telegram, Reddit, Email |
-| `onShare` callback | ✅ | `(platform, url) => {}` |
-| `onCopy` callback | ✅ | `(url) => {}` |
-| `theme: 'dark' \| 'light'` | ✅ | Basic two-mode theming |
-| `buttonColor` / `buttonHoverColor` | ✅ | Programmatic color overrides |
-| `customClass` | ✅ | Escape hatch for custom CSS |
-| `modalPosition` | ✅ | Modal placement config |
-| `updateOptions()` | ✅ | SPA dynamic URL updates |
-| React wrapper | ✅ | Exists as `src/social-share-button-react.jsx` (copy-paste only) |
-| TypeScript types | ❌ | None shipped |
-| Scoped npm package | ❌ | Not yet (`@social-share/core` etc.) |
-| Framework packages | ❌ | No installable Vue / Qwik / Solid packages |
-| Proper CSS build artifact | ❌ | CSS imported from `src/` path — breaks in most bundlers |
+| Feature | Status | Notes |
+| ---------------------------------- | ------ | --------------------------------------------------------------- |
+| CDN distribution (jsDelivr) | ✅ | `v1.0.3` |
+| npm package | ✅ | Published as `social-share-button-aossie` (unscoped) |
+| 7 share platforms | ✅ | WhatsApp, Facebook, X, LinkedIn, Telegram, Reddit, Email |
+| `onShare` callback | ✅ | `(platform, url) => {}` |
+| `onCopy` callback | ✅ | `(url) => {}` |
+| `theme: 'dark' \| 'light'` | ✅ | Basic two-mode theming |
+| `buttonColor` / `buttonHoverColor` | ✅ | Programmatic color overrides |
+| `customClass` | ✅ | Escape hatch for custom CSS |
+| `modalPosition` | ✅ | Modal placement config |
+| `updateOptions()` | ✅ | SPA dynamic URL updates |
+| React wrapper | ✅ | Exists as `src/social-share-button-react.jsx` (copy-paste only) |
+| TypeScript types | ❌ | None shipped |
+| Scoped npm package | ❌ | Not yet (`@social-share/core` etc.) |
+| Framework packages | ❌ | No installable Vue / Qwik / Solid packages |
+| Proper CSS build artifact | ❌ | CSS imported from `src/` path — breaks in most bundlers |
### ⚠️ Known Issues to Fix Before Any New Features
@@ -108,13 +108,13 @@ social-share-button/ ← Turborepo monorepo root
### Layer Responsibilities
-| Layer | Package | Responsibility |
-|---|---|---|
-| Core Engine | `@social-share/core` | Platform logic, URL building, config validation |
-| Analytics | `@social-share/analytics` | Event emission, consent, adapter routing |
-| Theme | `@social-share/theme` | CSS tokens, presets, Theme Designer, export |
-| Framework Wrappers | `@social-share/react` etc. | Framework-specific components using core |
-| CDN Build | `apps/cdn-build` | Bundles core + wrappers into single distributable |
+| Layer | Package | Responsibility |
+| ------------------ | -------------------------- | ------------------------------------------------- |
+| Core Engine | `@social-share/core` | Platform logic, URL building, config validation |
+| Analytics | `@social-share/analytics` | Event emission, consent, adapter routing |
+| Theme | `@social-share/theme` | CSS tokens, presets, Theme Designer, export |
+| Framework Wrappers | `@social-share/react` etc. | Framework-specific components using core |
+| CDN Build | `apps/cdn-build` | Bundles core + wrappers into single distributable |
---
@@ -122,7 +122,7 @@ social-share-button/ ← Turborepo monorepo root
---
-### ✅ Phase 0 — Stabilization *(Now — before any refactoring)*
+### ✅ Phase 0 — Stabilization _(Now — before any refactoring)_
**Goal:** Fix the broken npm package experience and document the full API surface. No new features.
@@ -181,6 +181,7 @@ export function executeShare(platform: Platform, config: ShareConfig): void { ..
**Goal:** Replace the copy-paste React wrapper with installable framework packages. React first since it already exists as `.jsx`.
**Priority order:**
+
1. `@social-share/react` — replaces `src/social-share-button-react.jsx`
2. `@social-share/vue` — Composition API component
3. `@social-share/qwik` — Resumable, SSR-safe (open issue)
@@ -191,26 +192,27 @@ export function executeShare(platform: Platform, config: ShareConfig): void { ..
```typescript
interface SocialShareButtonProps {
- url?: string; // default: window.location.href
- title?: string; // default: document.title
+ url?: string; // default: window.location.href
+ title?: string; // default: document.title
description?: string;
hashtags?: string[];
via?: string;
platforms?: Platform[];
buttonText?: string;
- buttonStyle?: 'default' | 'primary' | 'compact' | 'icon-only';
- buttonColor?: string; // existing API
- buttonHoverColor?: string; // existing API
- customClass?: string; // existing API
- theme?: 'dark' | 'light' | ThemeTokens; // string shorthand still works
- analytics?: AnalyticsConfig; // Phase 3
+ buttonStyle?: "default" | "primary" | "compact" | "icon-only";
+ buttonColor?: string; // existing API
+ buttonHoverColor?: string; // existing API
+ customClass?: string; // existing API
+ theme?: "dark" | "light" | ThemeTokens; // string shorthand still works
+ analytics?: AnalyticsConfig; // Phase 3
componentId?: string;
- onShare?: (platform: Platform, url: string) => void; // same signature as today
- onCopy?: (url: string) => void; // same signature as today
+ onShare?: (platform: Platform, url: string) => void; // same signature as today
+ onCopy?: (url: string) => void; // same signature as today
}
```
**Migration for existing React wrapper users:**
+
```tsx
// Before (copy-paste)
import { SocialShareButton } from "./components/SocialShareButton";
@@ -226,7 +228,7 @@ import { SocialShareButton } from "@social-share/react";
---
-### 🔬 Phase 3 — Analytics Module
+### 🔬 Phase 3 — Analytics Module
**Goal:** Ship `@social-share/analytics` — a privacy-first, pluggable analytics layer that uses the existing `onShare` / `onCopy` callbacks as its internal trigger mechanism.
@@ -234,25 +236,25 @@ import { SocialShareButton } from "@social-share/react";
```typescript
// Path 1: DOM Events — zero config, works with any analytics tool
-document.addEventListener('ssb:share', (e) => console.log(e.detail));
-document.addEventListener('ssb:copy', (e) => console.log(e.detail));
-document.addEventListener('ssb:modal_open', (e) => console.log(e.detail));
-document.addEventListener('ssb:modal_close', (e) => console.log(e.detail));
+document.addEventListener("ssb:share", (e) => console.log(e.detail));
+document.addEventListener("ssb:copy", (e) => console.log(e.detail));
+document.addEventListener("ssb:modal_open", (e) => console.log(e.detail));
+document.addEventListener("ssb:modal_close", (e) => console.log(e.detail));
// Path 2: Single callback hook — simplest npm integration
new SocialShareButton({
analytics: {
- onEvent: (event: SSBEvent) => myAnalytics.track(event.name, event.data)
- }
+ onEvent: (event: SSBEvent) => myAnalytics.track(event.name, event.data),
+ },
});
// Path 3: Named adapter — built-in wiring for popular tools
-import { GA4Adapter } from '@social-share/analytics/adapters/ga4';
+import { GA4Adapter } from "@social-share/analytics/adapters/ga4";
new SocialShareButton({
analytics: {
- adapter: new GA4Adapter({ measurementId: 'G-XXXXXXXX' })
- }
+ adapter: new GA4Adapter({ measurementId: "G-XXXXXXXX" }),
+ },
});
```
@@ -260,31 +262,31 @@ new SocialShareButton({
```typescript
interface SSBEvent {
- name: 'ssb:share' | 'ssb:copy' | 'ssb:modal_open' | 'ssb:modal_close';
+ name: "ssb:share" | "ssb:copy" | "ssb:modal_open" | "ssb:modal_close";
platform?: Platform;
- componentId?: string; // developer-defined identifier
+ componentId?: string; // developer-defined identifier
url: string;
timestamp: number;
- sessionId: string; // anonymous, ephemeral, never persisted
+ sessionId: string; // anonymous, ephemeral, never persisted
}
```
#### Built-in Adapters
-| Adapter | Notes |
-|---|---|
-| `GA4Adapter` | `gtag('event', ...)` — no PII sent |
-| `MixpanelAdapter` | `mixpanel.track()` — requires Mixpanel loaded |
-| `SegmentAdapter` | `analytics.track()` — wraps Segment's standard API |
-| `PlausibleAdapter` | `plausible()` custom event — cookieless by default |
-| `PostHogAdapter` | `posthog.capture()` — EU cloud + self-hosted compatible |
-| `CustomAdapter` | Extend `BaseAdapter`, implement `track(event: SSBEvent): void` |
+| Adapter | Notes |
+| ------------------ | -------------------------------------------------------------- |
+| `GA4Adapter` | `gtag('event', ...)` — no PII sent |
+| `MixpanelAdapter` | `mixpanel.track()` — requires Mixpanel loaded |
+| `SegmentAdapter` | `analytics.track()` — wraps Segment's standard API |
+| `PlausibleAdapter` | `plausible()` custom event — cookieless by default |
+| `PostHogAdapter` | `posthog.capture()` — EU cloud + self-hosted compatible |
+| `CustomAdapter` | Extend `BaseAdapter`, implement `track(event: SSBEvent): void` |
#### Privacy Controls
```typescript
-SocialShareButton.analytics.enable(); // call after consent granted
-SocialShareButton.analytics.disable(); // opt-out / consent withdrawn
+SocialShareButton.analytics.enable(); // call after consent granted
+SocialShareButton.analytics.disable(); // opt-out / consent withdrawn
SocialShareButton.analytics.isEnabled(); // boolean
SocialShareButton.analytics.debug(true); // log to console, don't send
```
@@ -298,7 +300,7 @@ SocialShareButton.analytics.debug(true); // log to console, don't send
---
-### 🎨 Phase 4 — Theme System + Theme Designer
+### 🎨 Phase 4 — Theme System + Theme Designer
**Goal:** Extend existing `dark` / `light` theming into a full CSS-variable-based system with an interactive Theme Designer. All existing `theme`, `buttonColor`, `buttonHoverColor`, `customClass` options remain fully supported.
@@ -311,7 +313,7 @@ SocialShareButton.analytics.debug(true); // log to console, don't send
--ssb-btn-bg: #1da1f2;
--ssb-btn-bg-hover: #0d8fd9;
--ssb-btn-radius: 6px;
- --ssb-btn-shadow: 0 2px 8px rgba(0,0,0,0.12);
+ --ssb-btn-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
/* Per-platform icon colors */
--ssb-icon-twitter: #1da1f2;
@@ -327,7 +329,7 @@ SocialShareButton.analytics.debug(true); // log to console, don't send
--ssb-modal-animation-speed: 200ms;
--ssb-font-family: system-ui, sans-serif;
- --ssb-shape: rounded; /* rounded | pill | square */
+ --ssb-shape: rounded; /* rounded | pill | square */
}
```
@@ -336,6 +338,7 @@ SocialShareButton.analytics.debug(true); // log to console, don't send
Hosted at `apps/playground`.
**Controls:**
+
- Colors & Gradients — solid + gradient builder per button
- Shapes — rounded / pill / square
- Border — width, color, style
@@ -344,6 +347,7 @@ Hosted at `apps/playground`.
- Font family, shadow intensity, hover effect selector
**Export formats:**
+
- CSS variables block
- `.json` theme file (SDK-importable)
- Shareable URL (theme as URL params)
@@ -369,38 +373,41 @@ import myTheme from './my-theme.json'; // from Theme Designer
**Goal:** CDN and SDK reach full feature parity. Complete all planned server-side and CMS integrations.
**CDN feature parity:**
+
- Bundle includes analytics + theming (opt-in at build config level)
- Config via `data-ssb-*` HTML attributes or `window.SocialShareButtonConfig`
- SRI hash generation in CI, hosted on jsDelivr + unpkg
**Platform integrations:**
-| Integration | Delivery | Issue Status |
-|---|---|---|
-| Remix | `@social-share/react` + SSR guide | 🟡 Open |
-| Solid.js | `@social-share/solid` | 🟡 Open |
-| Rails | Gem wrapper + CDN tag helper | 🟡 Open |
-| Django | Template tag + CDN | 🟡 Open |
-| Laravel | Blade component + CDN | 🟡 Open |
-| WordPress | Plugin (CDN-backed) | 🟡 Open |
-| Hugo | Shortcode + CDN | 🟡 Open |
-| Jekyll | Include template + CDN | 🟡 Open |
-| Web Components (Lit) | `@social-share/wc` | 🟡 Open |
-| Alpine.js | `x-data` binding guide + CDN | 🟡 Open |
+| Integration | Delivery | Issue Status |
+| -------------------- | --------------------------------- | ------------ |
+| Remix | `@social-share/react` + SSR guide | 🟡 Open |
+| Solid.js | `@social-share/solid` | 🟡 Open |
+| Rails | Gem wrapper + CDN tag helper | 🟡 Open |
+| Django | Template tag + CDN | 🟡 Open |
+| Laravel | Blade component + CDN | 🟡 Open |
+| WordPress | Plugin (CDN-backed) | 🟡 Open |
+| Hugo | Shortcode + CDN | 🟡 Open |
+| Jekyll | Include template + CDN | 🟡 Open |
+| Web Components (Lit) | `@social-share/wc` | 🟡 Open |
+| Alpine.js | `x-data` binding guide + CDN | 🟡 Open |
CI smoke test per integration: spin up minimal app, assert button renders and emits share event.
---
-### ⚡ Phase 6 — Advanced Features & Ecosystem
+### ⚡ Phase 6 — Advanced Features & Ecosystem
**Accessibility:**
+
- Full ARIA — `role="button"`, `aria-label`, `aria-expanded` on modal
- Keyboard navigation — Tab, Enter, Escape
- `prefers-reduced-motion` support (maps to `--ssb-modal-animation-speed: 0ms`)
- WCAG 2.1 AA tested with axe-core in CI
**Performance:**
+
- CDN bundle: target < 8KB gzipped
- npm packages: tree-shakeable — twitter-only import ~1KB
- CSS: single `@layer` block, no `@import` chains
@@ -409,22 +416,24 @@ CI smoke test per integration: spin up minimal app, assert button renders and em
```typescript
SocialShareButton.registerPlatform({
- id: 'bluesky',
- label: 'Bluesky',
+ id: "bluesky",
+ label: "Bluesky",
icon: BlueskyIcon,
buildURL: (config) => `https://bsky.app/intent/compose?text=${config.title} ${config.url}`,
});
```
**Native Web Share API:**
+
```typescript
new SocialShareButton({
- preferNativeShare: 'mobile-only', // true | false | 'mobile-only'
+ preferNativeShare: "mobile-only", // true | false | 'mobile-only'
});
// Falls back to custom modal when navigator.share() is unavailable
```
**Monorepo tooling maturity:**
+
- Changesets-based automated releases via GitHub Actions
- Per-package changelogs
- Canary / beta release channel
@@ -434,20 +443,20 @@ new SocialShareButton({
## 🤝 Contribution Opportunities
-| Area | Skills Needed | Phase |
-|---|---|---|
-| Fix CSS export path + `exports` field | npm packaging | 0 |
-| Write `.d.ts` TypeScript declarations | TypeScript | 0 |
-| Core engine extraction | TypeScript, DOM APIs | 1 |
-| Turborepo + pnpm workspace setup | Monorepo tooling | 1 |
-| `@social-share/react` (from existing jsx) | React | 2 |
-| `@social-share/vue` / `solid` / `qwik` | Vue / Solid / Qwik | 2 |
-| Analytics adapters | GA4 / PostHog / Segment APIs | 3 |
-| Theme Designer UI | React, CSS variables | 4 |
-| CMS / server-side integrations | Rails / Django / Laravel / WP | 5 |
-| Accessibility audit | WCAG, axe-core | 6 |
-| Docs site | Next.js, MDX | Ongoing |
-| CI/CD pipelines | GitHub Actions, Changesets | Ongoing |
+| Area | Skills Needed | Phase |
+| ----------------------------------------- | ----------------------------- | ------- |
+| Fix CSS export path + `exports` field | npm packaging | 0 |
+| Write `.d.ts` TypeScript declarations | TypeScript | 0 |
+| Core engine extraction | TypeScript, DOM APIs | 1 |
+| Turborepo + pnpm workspace setup | Monorepo tooling | 1 |
+| `@social-share/react` (from existing jsx) | React | 2 |
+| `@social-share/vue` / `solid` / `qwik` | Vue / Solid / Qwik | 2 |
+| Analytics adapters | GA4 / PostHog / Segment APIs | 3 |
+| Theme Designer UI | React, CSS variables | 4 |
+| CMS / server-side integrations | Rails / Django / Laravel / WP | 5 |
+| Accessibility audit | WCAG, axe-core | 6 |
+| Docs site | Next.js, MDX | Ongoing |
+| CI/CD pipelines | GitHub Actions, Changesets | Ongoing |
> 💡 Phase 0 tasks are labeled `good-first-issue` and require no monorepo knowledge — ideal starting point for new contributors.
@@ -465,16 +474,16 @@ new SocialShareButton({
## 📊 Distribution Strategy
-| Path | Audience | Package | Status |
-|---|---|---|---|
-| CDN (`
diff --git a/src/social-share-button-preact.jsx b/src/social-share-button-preact.jsx
index 012e66f..3d9b7cf 100644
--- a/src/social-share-button-preact.jsx
+++ b/src/social-share-button-preact.jsx
@@ -1,6 +1,6 @@
import { useEffect, useRef } from "preact/hooks";
- /**
+/**
* SocialShareButton Preact Wrapper
*
* Provides a lightweight Preact functional component that wraps the core
@@ -26,7 +26,7 @@ export default function SocialShareButton({
onCopy = null,
buttonStyle = "default",
modalPosition = "center",
-
+
// Analytics props — the library emits events but never collects data itself.
analytics = true,
onAnalytics = null, // (payload) => void hook
@@ -36,10 +36,10 @@ export default function SocialShareButton({
}) {
// DOM reference to the container where the button will be injected
const containerRef = useRef(null);
-
+
// Reference to the vanilla JS class instance
const shareButtonRef = useRef(null);
-
+
// Storage for the latest options to avoid stale closures during async initialization
const latestOptionsRef = useRef(null);
@@ -74,9 +74,9 @@ export default function SocialShareButton({
/**
* Initialization Effect
- *
+ *
* Handles the setup of the vanilla JS instance once the component mounts.
- * Includes a polling mechanism to wait for the core library if it's loaded
+ * Includes a polling mechanism to wait for the core library if it's loaded
* asynchronously (e.g., via a CDN script tag).
*/
useEffect(() => {
@@ -131,12 +131,12 @@ export default function SocialShareButton({
/**
* Update Effect
- *
- * Synchronizes prop changes from Preact down to the vanilla JS instance
+ *
+ * Synchronizes prop changes from Preact down to the vanilla JS instance
* without re-mounting the entire component.
*/
-
- // Stringify array dependencies to prevent unnecessary re-runs when
+
+ // Stringify array dependencies to prevent unnecessary re-runs when
// parent components pass fresh array literals on every render.
const hashtagsDep = JSON.stringify(hashtags);
const platformsDep = JSON.stringify(platforms);
diff --git a/src/social-share-button-react.jsx b/src/social-share-button-react.jsx
index 24eb3b2..45ac8f8 100644
--- a/src/social-share-button-react.jsx
+++ b/src/social-share-button-react.jsx
@@ -3,8 +3,8 @@ import { useEffect, useRef } from "react";
/**
* SocialShareButton React Wrapper
*
- * Provides a React functional component that wraps the core SocialShareButton
- * vanilla JS library. Handles lifecycle, dynamic updates, and provides
+ * Provides a React functional component that wraps the core SocialShareButton
+ * vanilla JS library. Handles lifecycle, dynamic updates, and provides
* sensible defaults for all sharing options.
*/
export const SocialShareButton = ({
@@ -21,7 +21,7 @@ export const SocialShareButton = ({
onCopy = null,
buttonStyle = "default",
modalPosition = "center",
-
+
// Analytics: library emits events but never collects data
analytics = true,
onAnalytics = null, // Event callback
@@ -31,7 +31,7 @@ export const SocialShareButton = ({
}) => {
// DOM reference for the injection target
const containerRef = useRef(null);
-
+
// Reference to the vanilla JS class instance
const shareButtonRef = useRef(null);
@@ -41,52 +41,52 @@ export const SocialShareButton = ({
/**
* Initialization Effect
- *
+ *
* Sets up the vanilla JS component once the React component mounts.
* Includes a safe check for the global SocialShareButton class.
*/
useEffect(() => {
- if (containerRef.current && !shareButtonRef.current) {
- if (typeof window !== "undefined" && window.SocialShareButton) {
- shareButtonRef.current = new window.SocialShareButton({
- container: containerRef.current,
- url: currentUrl,
- title: currentTitle,
- description,
- hashtags,
- via,
- platforms,
- theme,
- buttonText,
- customClass,
- onShare,
- onCopy,
- buttonStyle,
- modalPosition,
- analytics,
- onAnalytics,
- analyticsPlugins,
- componentId,
- debug,
- });
- }
+ if (containerRef.current && !shareButtonRef.current) {
+ if (typeof window !== "undefined" && window.SocialShareButton) {
+ shareButtonRef.current = new window.SocialShareButton({
+ container: containerRef.current,
+ url: currentUrl,
+ title: currentTitle,
+ description,
+ hashtags,
+ via,
+ platforms,
+ theme,
+ buttonText,
+ customClass,
+ onShare,
+ onCopy,
+ buttonStyle,
+ modalPosition,
+ analytics,
+ onAnalytics,
+ analyticsPlugins,
+ componentId,
+ debug,
+ });
}
+ }
- return () => {
- if (shareButtonRef.current) {
- shareButtonRef.current.destroy();
- shareButtonRef.current = null;
- }
- };
- }, []);
+ return () => {
+ if (shareButtonRef.current) {
+ shareButtonRef.current.destroy();
+ shareButtonRef.current = null;
+ }
+ };
+ }, []);
/**
* Update Effect
- *
- * Synchronizes React prop changes with the underlying vanilla JS instance
+ *
+ * Synchronizes React prop changes with the underlying vanilla JS instance
* without re-mounting the entire component.
*/
-
+
useEffect(() => {
if (shareButtonRef.current) {
// Use the library's built-in update method
diff --git a/src/social-share-button.css b/src/social-share-button.css
index d42c10b..0609839 100644
--- a/src/social-share-button.css
+++ b/src/social-share-button.css
@@ -238,7 +238,6 @@
background: rgba(255, 255, 255, 0.4);
}
-
/* Individual Platform Button */
.social-share-platform-btn {
display: flex;
@@ -255,8 +254,8 @@
}
.social-share-platform-btn:hover {
- transform: scale(1.05);
- }
+ transform: scale(1.05);
+}
.social-share-platform-btn:active {
transform: scale(0.95);
@@ -278,10 +277,10 @@
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
-.social-share-platform-icon svg {
+.social-share-platform-icon svg {
width: 28px;
- height: 28px;
- }
+ height: 28px;
+}
.social-share-platform-btn span {
font-size: 12px;
@@ -375,7 +374,6 @@
background: #00b857;
}
-
/* -----------------------------------------------------------------------------
RESPONSIVE OVERRIDES
----------------------------------------------------------------------------- */
@@ -389,11 +387,10 @@
}
/* Snap modal to bottom on mobile */
- .social-share-modal-content.bottom {
- margin-top: auto;
+ .social-share-modal-content.bottom {
+ margin-top: auto;
}
-
.social-share-modal-header {
padding: 12px 16px;
}
@@ -450,7 +447,6 @@
color: #fff;
}
-
/* -----------------------------------------------------------------------------
ACCESSIBILITY & PRINT
----------------------------------------------------------------------------- */
diff --git a/src/social-share-button.js b/src/social-share-button.js
index 3905128..24fc604 100644
--- a/src/social-share-button.js
+++ b/src/social-share-button.js
@@ -85,15 +85,10 @@ class SocialShareButton {
`;
this.button = button;
- if (this.options.container) {
- const container =
- typeof this.options.container === "string"
- ? document.querySelector(this.options.container)
- : this.options.container;
+ const container = this._getContainer();
- if (container) {
- container.appendChild(button);
- }
+ if (container) {
+ container.appendChild(button);
}
}
@@ -584,6 +579,18 @@ class SocialShareButton {
this.eventsAttached = false; // Reset re-entrancy guard
}
+ /**
+ * Updates specific library settings dynamically after initialization.
+ *
+ * NOTE: Currently, this method only supports updating non-structural properties:
+ * `url`, `buttonColor`, and `buttonHoverColor`.
+ * Changing structural or DOM-dependent options (such as `theme`, `buttonText`,
+ * `buttonStyle`, `platforms`, or `analytics`) will update the internal options
+ * object, but requires destroying and recreating the button instance to fully
+ * reflect the changes in the DOM.
+ *
+ * @param {Object} options - Partial options object to merge.
+ */
updateOptions(options) {
this.options = { ...this.options, ...options };
@@ -684,9 +691,32 @@ class SocialShareButton {
_getContainer() {
if (!this.options.container) return null;
if (typeof document === "undefined") return null;
- return typeof this.options.container === "string"
- ? document.querySelector(this.options.container)
- : this.options.container;
+
+ let container = null;
+ try {
+ container =
+ typeof this.options.container === "string"
+ ? document.querySelector(this.options.container)
+ : this.options.container;
+ } catch (error) {
+ this._debugWarn("Invalid container selector provided in _getContainer", error);
+ return null;
+ }
+
+ // Safety check: ensure the resolved value is actually a DOM Element.
+ // This prevents crashes if a user passes a non-DOM object to the container option.
+ if (
+ container &&
+ !(
+ (typeof Element !== "undefined" && container instanceof Element) ||
+ container.nodeType === 1
+ )
+ ) {
+ this._debugWarn(`Provided container is not a valid DOM Element: ${container}`, null);
+ return null;
+ }
+
+ return container;
}
/**
@@ -799,4 +829,4 @@ if (typeof module !== "undefined" && module.exports) {
if (typeof window !== "undefined") {
window.SocialShareButton = SocialShareButton;
-}
\ No newline at end of file
+}