diff --git a/.wp-env.json b/.wp-env.json
index dafab87..b535397 100644
--- a/.wp-env.json
+++ b/.wp-env.json
@@ -1,5 +1,6 @@
{
"plugins": [
"."
- ]
+ ],
+ "testsEnvironment": false
}
diff --git a/README.md b/README.md
index 23ff1d0..8781b9f 100644
--- a/README.md
+++ b/README.md
@@ -90,9 +90,44 @@ add_filter( 'blockparty_post_sharing_breakpoint', function () {
- **Parameters:** `int` — Breakpoint width in pixels.
- **Default:** `600`
-### Icon customization
+### Share networks (fallback menu)
-Default icons are exposed as CSS custom properties on `.wp-block-blockparty-post-sharing-button`:
+When the Web Share API is unavailable, the Share button opens a menu of social networks. Themes and plugins can customize the list with:
+
+```php
+add_filter( 'blockparty_post_sharing_networks', function ( $networks, $url, $title, $post_id ) {
+ // Remove WhatsApp.
+ $networks = array_values(
+ array_filter(
+ $networks,
+ static function ( $network ) {
+ return ( $network['id'] ?? '' ) !== 'whatsapp';
+ }
+ )
+ );
+
+ // Add Mastodon.
+ $networks[] = [
+ 'id' => 'mastodon',
+ 'label' => 'Mastodon',
+ 'url' => 'https://mastodon.social/share?text=' . rawurlencode( $title . ' ' . $url ),
+ ];
+
+ return $networks;
+}, 10, 4 );
+```
+
+- **Filter name:** `blockparty_post_sharing_networks`
+- **Parameters:**
+ - `array $networks` — List of networks (`id`, `label`, `url`)
+ - `string $url` — Post permalink
+ - `string $title` — Post title
+ - `int $post_id` — Post ID
+- **Default:** Facebook, X, Bluesky, LinkedIn, WhatsApp
+
+### Icon and menu customization
+
+Default icons and the share fallback menu are exposed as CSS custom properties on `.wp-block-blockparty-post-sharing-button`:
```css
.wp-block-blockparty-post-sharing-button {
@@ -101,6 +136,30 @@ Default icons are exposed as CSS custom properties on `.wp-block-blockparty-post
--wp-block-blockparty-post-sharing-button-check-icon: url( '/path/to/check.svg' );
--wp-block-blockparty-post-sharing-button-icon-size: 1.25rem;
--wp-block-blockparty-post-sharing-button-icon-color: currentColor;
+
+ /* Share fallback menu */
+ --wp-block-blockparty-post-sharing-button-menu-offset: 0.5rem;
+ --wp-block-blockparty-post-sharing-button-menu-min-width: 12rem;
+ --wp-block-blockparty-post-sharing-button-menu-padding: 0.5rem;
+ --wp-block-blockparty-post-sharing-button-menu-gap: 0.25rem;
+ --wp-block-blockparty-post-sharing-button-menu-border-width: 1px;
+ --wp-block-blockparty-post-sharing-button-menu-border-style: solid;
+ --wp-block-blockparty-post-sharing-button-menu-border-color: currentColor;
+ --wp-block-blockparty-post-sharing-button-menu-border-radius: 0.25rem;
+ --wp-block-blockparty-post-sharing-button-menu-bg: #fff;
+ --wp-block-blockparty-post-sharing-button-menu-color: currentColor;
+ --wp-block-blockparty-post-sharing-button-menu-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.12);
+ --wp-block-blockparty-post-sharing-button-menu-link-padding: 0.5rem 0.75rem;
+ --wp-block-blockparty-post-sharing-button-menu-link-border-radius: 0.125rem;
+ --wp-block-blockparty-post-sharing-button-menu-link-hover-bg: rgba(0, 0, 0, 0.06);
+ --wp-block-blockparty-post-sharing-button-menu-link-gap: 0.5rem;
+ --wp-block-blockparty-post-sharing-button-menu-icon-size: 1.25rem;
+ --wp-block-blockparty-post-sharing-button-menu-icon-color: currentColor;
+ --wp-block-blockparty-post-sharing-button-menu-facebook-icon: url( '/path/to/facebook.svg' );
+ --wp-block-blockparty-post-sharing-button-menu-x-icon: url( '/path/to/x.svg' );
+ --wp-block-blockparty-post-sharing-button-menu-bluesky-icon: url( '/path/to/bluesky.svg' );
+ --wp-block-blockparty-post-sharing-button-menu-linkedin-icon: url( '/path/to/linkedin.svg' );
+ --wp-block-blockparty-post-sharing-button-menu-whatsapp-icon: url( '/path/to/whatsapp.svg' );
}
```
@@ -121,7 +180,8 @@ blockparty-post-sharing/
│ └── style.scss # Frontend and editor styles
├── includes/ # PHP classes
│ ├── BlockRenderer.php # Dynamic block rendering
-│ └── ResponsiveDisplay.php # Responsive visibility rules
+│ ├── ResponsiveDisplay.php # Responsive visibility rules
+│ └── ShareNetworks.php # Share fallback networks (filterable)
├── build/ # Compiled assets (blocks-manifest.php, etc.)
├── languages/ # Translation files
├── .wordpress-org/blueprints/ # WordPress Playground blueprint
diff --git a/blockparty-post-sharing.php b/blockparty-post-sharing.php
index 140b904..285eb0e 100644
--- a/blockparty-post-sharing.php
+++ b/blockparty-post-sharing.php
@@ -22,7 +22,11 @@
}
if ( is_readable( __DIR__ . '/vendor/autoload.php' ) ) {
- include_once __DIR__ . '/vendor/autoload.php';
+ require_once __DIR__ . '/vendor/autoload.php';
+} else {
+ require_once __DIR__ . '/includes/BlockRenderer.php';
+ require_once __DIR__ . '/includes/ResponsiveDisplay.php';
+ require_once __DIR__ . '/includes/ShareNetworks.php';
}
define( 'BLOCKPARTY_POST_SHARING_VERSION', '1.0.0' );
diff --git a/includes/BlockRenderer.php b/includes/BlockRenderer.php
index 9b40ef2..4768cc0 100644
--- a/includes/BlockRenderer.php
+++ b/includes/BlockRenderer.php
@@ -63,6 +63,9 @@ public static function render( $attributes ) {
'data-url' => esc_url( $url ),
'data-title' => esc_attr( $title ),
'data-copied-label' => esc_attr( $copied_label ),
+ 'data-networks' => wp_json_encode(
+ ShareNetworks::get_networks( $url, $title, (int) $post_id )
+ ),
];
if ( ! empty( $wrapper_classes ) ) {
diff --git a/includes/ShareNetworks.php b/includes/ShareNetworks.php
new file mode 100644
index 0000000..0790bd6
--- /dev/null
+++ b/includes/ShareNetworks.php
@@ -0,0 +1,107 @@
+
+ */
+ public static function get_networks( string $url, string $title = '', int $post_id = 0 ): array {
+ $encoded_url = rawurlencode( $url );
+ $encoded_title = rawurlencode( $title );
+ $encoded_text = rawurlencode( '' !== $title ? $title . ' ' . $url : $url );
+
+ $networks = [
+ [
+ 'id' => 'facebook',
+ 'label' => 'Facebook',
+ 'url' => 'https://www.facebook.com/sharer/sharer.php?u=' . $encoded_url,
+ ],
+ [
+ 'id' => 'x',
+ 'label' => 'X',
+ 'url' => 'https://x.com/intent/post?url=' . $encoded_url . '&text=' . $encoded_title,
+ ],
+ [
+ 'id' => 'bluesky',
+ 'label' => 'Bluesky',
+ 'url' => 'https://bsky.app/intent/compose?text=' . $encoded_text,
+ ],
+ [
+ 'id' => 'linkedin',
+ 'label' => 'LinkedIn',
+ 'url' => 'https://www.linkedin.com/sharing/share-offsite/?url=' . $encoded_url,
+ ],
+ [
+ 'id' => 'whatsapp',
+ 'label' => 'WhatsApp',
+ 'url' => 'https://api.whatsapp.com/send?text=' . $encoded_text,
+ ],
+ ];
+
+ /**
+ * Filter social networks used in the share fallback menu.
+ *
+ * Each network should provide:
+ * - id (string): Network slug used as a CSS modifier.
+ * - label (string): Visible network name.
+ * - url (string): Absolute share URL for the current post.
+ *
+ * @param array $networks Social network definitions.
+ * @param string $url Post permalink.
+ * @param string $title Post title.
+ * @param int $post_id Post ID.
+ */
+ $networks = apply_filters(
+ 'blockparty_post_sharing_networks',
+ $networks,
+ $url,
+ $title,
+ $post_id
+ );
+
+ if ( ! is_array( $networks ) ) {
+ return [];
+ }
+
+ return array_values(
+ array_filter(
+ array_map( [ self::class, 'sanitize_network' ], $networks )
+ )
+ );
+ }
+
+ /**
+ * Sanitize a single network definition.
+ *
+ * @param mixed $network Network definition.
+ *
+ * @return array{id: string, label: string, url: string}|null
+ */
+ private static function sanitize_network( $network ): ?array {
+ if ( ! is_array( $network ) ) {
+ return null;
+ }
+
+ $id = isset( $network['id'] ) ? sanitize_key( (string) $network['id'] ) : '';
+ $label = isset( $network['label'] ) ? sanitize_text_field( (string) $network['label'] ) : '';
+ $url = isset( $network['url'] ) ? esc_url_raw( (string) $network['url'] ) : '';
+
+ if ( '' === $id || '' === $label || '' === $url ) {
+ return null;
+ }
+
+ return [
+ 'id' => $id,
+ 'label' => $label,
+ 'url' => $url,
+ ];
+ }
+}
diff --git a/package.json b/package.json
index 1f7a698..43e5706 100644
--- a/package.json
+++ b/package.json
@@ -15,8 +15,8 @@
"make-pot": "wp i18n make-pot . languages/blockparty-post-sharing.pot --exclude=\"src\" --domain=blockparty-post-sharing",
"make-json": "wp i18n make-json languages/blockparty-post-sharing-fr_FR.po languages/ --no-purge",
"start": "wp-scripts start --blocks-manifest",
- "start:env": "wp-env start",
- "stop:env": "wp-env stop"
+ "env:start": "wp-env start",
+ "env:stop": "wp-env stop"
},
"dependencies": {
"@wordpress/block-editor": "latest",
diff --git a/src/blockparty-post-sharing/editor.scss b/src/blockparty-post-sharing/editor.scss
index f4233ff..d60fa15 100644
--- a/src/blockparty-post-sharing/editor.scss
+++ b/src/blockparty-post-sharing/editor.scss
@@ -9,12 +9,9 @@
}
&.is-display-preview-desktop.hide-copy-on-desktop &__copy,
- &.is-display-preview-desktop.hide-share-on-desktop &__share {
- display: none;
- }
-
+ &.is-display-preview-desktop.hide-share-on-desktop &__share,
&.is-display-preview-mobile.hide-copy-on-mobile &__copy,
&.is-display-preview-mobile.hide-share-on-mobile &__share {
- display: none;
+ opacity: .5;
}
}
diff --git a/src/blockparty-post-sharing/img/bluesky.svg b/src/blockparty-post-sharing/img/bluesky.svg
new file mode 100644
index 0000000..0af4aa4
--- /dev/null
+++ b/src/blockparty-post-sharing/img/bluesky.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/blockparty-post-sharing/img/facebook.svg b/src/blockparty-post-sharing/img/facebook.svg
new file mode 100644
index 0000000..7e58df3
--- /dev/null
+++ b/src/blockparty-post-sharing/img/facebook.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/blockparty-post-sharing/img/linkedin.svg b/src/blockparty-post-sharing/img/linkedin.svg
new file mode 100644
index 0000000..8285a2d
--- /dev/null
+++ b/src/blockparty-post-sharing/img/linkedin.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/blockparty-post-sharing/img/whatsapp.svg b/src/blockparty-post-sharing/img/whatsapp.svg
new file mode 100644
index 0000000..337fc34
--- /dev/null
+++ b/src/blockparty-post-sharing/img/whatsapp.svg
@@ -0,0 +1 @@
+
diff --git a/src/blockparty-post-sharing/img/x.svg b/src/blockparty-post-sharing/img/x.svg
new file mode 100644
index 0000000..c4e3179
--- /dev/null
+++ b/src/blockparty-post-sharing/img/x.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/blockparty-post-sharing/style.scss b/src/blockparty-post-sharing/style.scss
index d31a033..40cb4bf 100644
--- a/src/blockparty-post-sharing/style.scss
+++ b/src/blockparty-post-sharing/style.scss
@@ -13,6 +13,28 @@
--wp-block-blockparty-post-sharing-button-copy-icon: url("./img/link.svg");
--wp-block-blockparty-post-sharing-button-share-icon: url("./img/share.svg");
--wp-block-blockparty-post-sharing-button-check-icon: url("./img/check.svg");
+ --wp-block-blockparty-post-sharing-button-menu-offset: 0.5rem;
+ --wp-block-blockparty-post-sharing-button-menu-min-width: 12rem;
+ --wp-block-blockparty-post-sharing-button-menu-padding: 0.5rem;
+ --wp-block-blockparty-post-sharing-button-menu-gap: 0.25rem;
+ --wp-block-blockparty-post-sharing-button-menu-border-width: 1px;
+ --wp-block-blockparty-post-sharing-button-menu-border-style: solid;
+ --wp-block-blockparty-post-sharing-button-menu-border-color: currentColor;
+ --wp-block-blockparty-post-sharing-button-menu-border-radius: 0.25rem;
+ --wp-block-blockparty-post-sharing-button-menu-bg: #fff;
+ --wp-block-blockparty-post-sharing-button-menu-color: currentColor;
+ --wp-block-blockparty-post-sharing-button-menu-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.12);
+ --wp-block-blockparty-post-sharing-button-menu-link-padding: 0.5rem 0.75rem;
+ --wp-block-blockparty-post-sharing-button-menu-link-border-radius: 0.125rem;
+ --wp-block-blockparty-post-sharing-button-menu-link-hover-bg: rgba(0, 0, 0, 0.06);
+ --wp-block-blockparty-post-sharing-button-menu-link-gap: 0.5rem;
+ --wp-block-blockparty-post-sharing-button-menu-icon-size: 1.25rem;
+ --wp-block-blockparty-post-sharing-button-menu-icon-color: currentColor;
+ --wp-block-blockparty-post-sharing-button-menu-facebook-icon: url("./img/facebook.svg");
+ --wp-block-blockparty-post-sharing-button-menu-x-icon: url("./img/x.svg");
+ --wp-block-blockparty-post-sharing-button-menu-bluesky-icon: url("./img/bluesky.svg");
+ --wp-block-blockparty-post-sharing-button-menu-linkedin-icon: url("./img/linkedin.svg");
+ --wp-block-blockparty-post-sharing-button-menu-whatsapp-icon: url("./img/whatsapp.svg");
&__actions {
display: flex;
@@ -65,6 +87,108 @@
mask-image: var(--wp-block-blockparty-post-sharing-button-share-icon);
}
+ &__share-wrap {
+ position: relative;
+ display: inline-flex;
+ }
+
+ &__share-menu {
+ position: absolute;
+ z-index: 10;
+ top: calc(100% + var(--wp-block-blockparty-post-sharing-button-menu-offset, 0.5rem));
+ left: 0;
+ min-width: var(--wp-block-blockparty-post-sharing-button-menu-min-width, 12rem);
+ padding: var(--wp-block-blockparty-post-sharing-button-menu-padding, 0.5rem);
+ border-width: var(--wp-block-blockparty-post-sharing-button-menu-border-width, 1px);
+ border-style: var(--wp-block-blockparty-post-sharing-button-menu-border-style, solid);
+ border-color: var(--wp-block-blockparty-post-sharing-button-menu-border-color, currentColor);
+ border-radius: var(--wp-block-blockparty-post-sharing-button-menu-border-radius, 0.25rem);
+ background-color: var(--wp-block-blockparty-post-sharing-button-menu-bg, #fff);
+ color: var(--wp-block-blockparty-post-sharing-button-menu-color, currentColor);
+ box-shadow: var(
+ --wp-block-blockparty-post-sharing-button-menu-shadow,
+ 0 0.25rem 0.75rem rgba(0, 0, 0, 0.12)
+ );
+ }
+
+ &__share-menu[hidden] {
+ display: none;
+ }
+
+ &__share-menu-list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--wp-block-blockparty-post-sharing-button-menu-gap, 0.25rem);
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ }
+
+ &__share-menu-item {
+ margin: 0;
+ }
+
+ &__share-menu-link {
+ display: flex;
+ align-items: center;
+ gap: var(--wp-block-blockparty-post-sharing-button-menu-link-gap, 0.5rem);
+ padding: var(--wp-block-blockparty-post-sharing-button-menu-link-padding, 0.5rem 0.75rem);
+ border-radius: var(
+ --wp-block-blockparty-post-sharing-button-menu-link-border-radius,
+ 0.125rem
+ );
+ color: inherit;
+ text-decoration: none;
+ line-height: 1.25;
+
+ &:hover,
+ &:focus-visible {
+ background-color: var(
+ --wp-block-blockparty-post-sharing-button-menu-link-hover-bg,
+ rgba(0, 0, 0, 0.06)
+ );
+ outline: none;
+ }
+ }
+
+ &__share-menu-icon {
+ display: block;
+ flex-shrink: 0;
+ width: var(--wp-block-blockparty-post-sharing-button-menu-icon-size, 1.25rem);
+ height: var(--wp-block-blockparty-post-sharing-button-menu-icon-size, 1.25rem);
+ background-color: var(
+ --wp-block-blockparty-post-sharing-button-menu-icon-color,
+ currentColor
+ );
+ mask-repeat: no-repeat;
+ mask-position: center;
+ mask-size: contain;
+ }
+
+ &__share-menu-link.is-facebook &__share-menu-icon {
+ mask-image: var(--wp-block-blockparty-post-sharing-button-menu-facebook-icon);
+ }
+
+ &__share-menu-link.is-x &__share-menu-icon {
+ mask-image: var(--wp-block-blockparty-post-sharing-button-menu-x-icon);
+ }
+
+ &__share-menu-link.is-bluesky &__share-menu-icon {
+ mask-image: var(--wp-block-blockparty-post-sharing-button-menu-bluesky-icon);
+ }
+
+ &__share-menu-link.is-linkedin &__share-menu-icon {
+ mask-image: var(--wp-block-blockparty-post-sharing-button-menu-linkedin-icon);
+ }
+
+ &__share-menu-link.is-whatsapp &__share-menu-icon {
+ mask-image: var(--wp-block-blockparty-post-sharing-button-menu-whatsapp-icon);
+ }
+
+ &__share-menu-label {
+ flex: 1 1 auto;
+ }
+
&__status {
margin: 0.5rem 0 0;
}
diff --git a/src/blockparty-post-sharing/view.js b/src/blockparty-post-sharing/view.js
index c869765..7da7280 100644
--- a/src/blockparty-post-sharing/view.js
+++ b/src/blockparty-post-sharing/view.js
@@ -1,7 +1,7 @@
/**
* Front-end behavior for the post sharing block.
*/
-import { __ } from '@wordpress/i18n';
+import { __, sprintf } from '@wordpress/i18n';
/**
* Copy text to the clipboard with a fallback for older browsers.
@@ -76,6 +76,236 @@ function showStatus( statusEl, message ) {
}, 3000 );
}
+/**
+ * Whether the Web Share API is available.
+ *
+ * @return {boolean} True when navigator.share is a function.
+ */
+function canUseNativeShare() {
+ return typeof navigator.share === 'function';
+}
+
+/**
+ * Parse share networks exposed by PHP on the block wrapper.
+ *
+ * @param {HTMLElement} block Block wrapper element.
+ * @return {Array<{id: string, label: string, url: string}>} Network definitions.
+ */
+function getShareNetworks( block ) {
+ const raw = block.dataset.networks;
+
+ if ( ! raw ) {
+ return [];
+ }
+
+ try {
+ const networks = JSON.parse( raw );
+
+ if ( ! Array.isArray( networks ) ) {
+ return [];
+ }
+
+ return networks.filter(
+ ( network ) =>
+ network &&
+ typeof network.id === 'string' &&
+ typeof network.label === 'string' &&
+ typeof network.url === 'string' &&
+ network.id &&
+ network.label &&
+ network.url
+ );
+ } catch {
+ return [];
+ }
+}
+
+/**
+ * Ensure the share button is wrapped for absolute tooltip positioning.
+ *
+ * @param {HTMLElement} shareButton Share button element.
+ * @return {HTMLElement} Wrapper element.
+ */
+function ensureShareWrap( shareButton ) {
+ const existing = shareButton.closest(
+ '.wp-block-blockparty-post-sharing-button__share-wrap'
+ );
+
+ if ( existing ) {
+ return existing;
+ }
+
+ const wrap = document.createElement( 'div' );
+ wrap.className = 'wp-block-blockparty-post-sharing-button__share-wrap';
+ shareButton.parentNode.insertBefore( wrap, shareButton );
+ wrap.appendChild( shareButton );
+
+ return wrap;
+}
+
+/**
+ * Create the fallback share tooltip menu.
+ *
+ * @param {Array<{id: string, label: string, url: string}>} networks Network definitions.
+ * @param {Function} onClose Callback invoked when the menu should close.
+ * @return {HTMLElement} Menu element.
+ */
+function createShareFallbackMenu( networks, onClose ) {
+ const menu = document.createElement( 'div' );
+ menu.className = 'wp-block-blockparty-post-sharing-button__share-menu';
+ menu.setAttribute( 'role', 'menu' );
+ menu.setAttribute(
+ 'aria-label',
+ __( 'Share on social networks', 'blockparty-post-sharing' )
+ );
+ menu.hidden = true;
+
+ const list = document.createElement( 'ul' );
+ list.className = 'wp-block-blockparty-post-sharing-button__share-menu-list';
+
+ networks.forEach( ( network ) => {
+ const item = document.createElement( 'li' );
+ item.className =
+ 'wp-block-blockparty-post-sharing-button__share-menu-item';
+ item.setAttribute( 'role', 'none' );
+
+ const link = document.createElement( 'a' );
+ link.className = `wp-block-blockparty-post-sharing-button__share-menu-link is-${ network.id }`;
+ link.setAttribute( 'role', 'menuitem' );
+ link.href = network.url;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.setAttribute(
+ 'aria-label',
+ sprintf(
+ /* translators: %s: social network name */
+ __( 'Share on %s', 'blockparty-post-sharing' ),
+ network.label
+ )
+ );
+ link.addEventListener( 'click', () => {
+ onClose();
+ } );
+
+ const icon = document.createElement( 'span' );
+ icon.className =
+ 'wp-block-blockparty-post-sharing-button__share-menu-icon';
+ icon.setAttribute( 'aria-hidden', 'true' );
+
+ const label = document.createElement( 'span' );
+ label.className =
+ 'wp-block-blockparty-post-sharing-button__share-menu-label';
+ label.textContent = network.label;
+
+ link.appendChild( icon );
+ link.appendChild( label );
+
+ item.appendChild( link );
+ list.appendChild( item );
+ } );
+
+ menu.appendChild( list );
+
+ return menu;
+}
+
+/**
+ * Bind fallback share tooltip behavior when Web Share API is unavailable.
+ *
+ * @param {HTMLElement} shareButton Share button element.
+ * @param {HTMLElement} menu Fallback menu element.
+ * @return {Function} Close menu callback.
+ */
+function bindShareFallback( shareButton, menu ) {
+ const closeMenu = () => {
+ menu.hidden = true;
+ shareButton.setAttribute( 'aria-expanded', 'false' );
+ };
+
+ const openMenu = () => {
+ menu.hidden = false;
+ shareButton.setAttribute( 'aria-expanded', 'true' );
+ const firstItem = menu.querySelector( '[role="menuitem"]' );
+ firstItem?.focus();
+ };
+
+ const toggleMenu = () => {
+ if ( menu.hidden ) {
+ openMenu();
+ } else {
+ closeMenu();
+ }
+ };
+
+ shareButton.setAttribute( 'aria-haspopup', 'menu' );
+ shareButton.setAttribute( 'aria-expanded', 'false' );
+ shareButton.setAttribute( 'aria-controls', menu.id );
+
+ shareButton.addEventListener( 'click', ( event ) => {
+ event.preventDefault();
+ event.stopPropagation();
+ toggleMenu();
+ } );
+
+ document.addEventListener( 'click', ( event ) => {
+ if ( menu.hidden ) {
+ return;
+ }
+
+ if (
+ ! menu.contains( event.target ) &&
+ ! shareButton.contains( event.target )
+ ) {
+ closeMenu();
+ }
+ } );
+
+ document.addEventListener( 'keydown', ( event ) => {
+ if ( event.key === 'Escape' && ! menu.hidden ) {
+ closeMenu();
+ shareButton.focus();
+ }
+ } );
+
+ return closeMenu;
+}
+
+/**
+ * Share via the native Web Share API.
+ *
+ * @param {string} url Post URL.
+ * @param {string} title Post title.
+ * @param {HTMLElement} statusEl Status element.
+ */
+async function shareNatively( url, title, statusEl ) {
+ try {
+ const shareData = { url };
+
+ if ( title ) {
+ shareData.title = title;
+ }
+
+ if ( navigator.canShare && ! navigator.canShare( shareData ) ) {
+ showStatus(
+ statusEl,
+ __( 'Unable to share', 'blockparty-post-sharing' )
+ );
+ return;
+ }
+
+ await navigator.share( shareData );
+ } catch ( error ) {
+ if ( error?.name === 'AbortError' ) {
+ return;
+ }
+
+ showStatus(
+ statusEl,
+ __( 'Unable to share', 'blockparty-post-sharing' )
+ );
+ }
+}
+
/**
* Initialize a single post sharing block instance.
*
@@ -96,10 +326,6 @@ function initBlock( block ) {
block.dataset.copiedLabel ||
__( 'Link copied', 'blockparty-post-sharing' );
- if ( ! navigator.share && shareButton ) {
- shareButton.hidden = true;
- }
-
copyButton.addEventListener( 'click', async () => {
try {
await copyToClipboard( url );
@@ -112,36 +338,33 @@ function initBlock( block ) {
}
} );
- if ( shareButton && navigator.share ) {
- shareButton.addEventListener( 'click', async () => {
- try {
- const shareData = { url };
-
- if ( title ) {
- shareData.title = title;
- }
-
- if ( navigator.canShare && ! navigator.canShare( shareData ) ) {
- showStatus(
- status,
- __( 'Unable to share', 'blockparty-post-sharing' )
- );
- return;
- }
-
- await navigator.share( shareData );
- } catch ( error ) {
- if ( error?.name === 'AbortError' ) {
- return;
- }
-
- showStatus(
- status,
- __( 'Unable to share', 'blockparty-post-sharing' )
- );
- }
+ if ( ! shareButton ) {
+ return;
+ }
+
+ if ( canUseNativeShare() ) {
+ shareButton.addEventListener( 'click', () => {
+ shareNatively( url, title, status );
} );
+ return;
}
+
+ const networks = getShareNetworks( block );
+
+ if ( ! networks.length ) {
+ return;
+ }
+
+ const wrap = ensureShareWrap( shareButton );
+ const menuId = `blockparty-post-sharing-menu-${ Math.random()
+ .toString( 36 )
+ .slice( 2, 10 ) }`;
+
+ let closeMenu = () => {};
+ const menu = createShareFallbackMenu( networks, () => closeMenu() );
+ menu.id = menuId;
+ wrap.appendChild( menu );
+ closeMenu = bindShareFallback( shareButton, menu );
}
document