Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .plugin-data
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"version": "1.0.1",
"version": "1.0.2",
"slug": "blockparty-modal"
}
38 changes: 33 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Blockparty Modal is a WordPress plugin that lets you add accessible modal dialog
- **Modal dialog**: Uses the native `<dialog>` element for semantics and accessibility
- **Configurable content**: Title (with heading level), rich text content, and optional close button
- **Close behaviour**: Choose how the modal closes — click outside, close button only, or prevent closing by backdrop
- **Trigger linking**: Link any block (e.g. core/button) to open a specific modal via `linkedModalId`
- **Trigger linking**: Link a block to open a specific modal via `linkedModalId`; by default only the Button block is allowed as a trigger (filterable)
- **Stable modal ID**: Each modal can have a unique ID for trigger association
- **Layout & styling**: Supports wide and full-width alignment, dimensions, colors, and spacing
- **Internationalized**: Multilingual support with translation files (French included)
Expand Down Expand Up @@ -70,11 +70,27 @@ npm run build
- **Close behaviour** — "Any" (click outside or close button), "Close button only", or "None"
- **Close button** — show or hide the close button
- **Prevent scroll** — lock body scroll when the modal is open
4. To open the modal from a button:
- Add a **Button** block (or another block that supports the modal trigger)
- In the block settings, set **Linked Modal ID** to the same value as the modal’s **Modal ID**
4. To open the modal from a trigger block:
- Add a **Button** block (by default, only the Button block can be a modal trigger)
- In the block sidebar, open **Attached modal** and select the modal to open
- On the frontend, clicking that button will open the corresponding modal

### Blocks allowed as modal triggers

By default, only the **core/button** block can be linked to a modal. To allow other blocks (e.g. paragraph, image, or custom blocks), use the filter `blockparty_modal_trigger_allowed_blocks` in your theme or plugin:

```php
add_filter( 'blockparty_modal_trigger_allowed_blocks', function ( $blocks ) {
$blocks[] = 'core/paragraph';
$blocks[] = 'my-plugin/cta';
return $blocks;
} );
```

- **Filter name:** `blockparty_modal_trigger_allowed_blocks`
- **Parameters:** `array` — List of block names (e.g. `'core/button'`).
- **Default:** `array( 'core/button' )`

## 🛠️ Development

### Project Structure
Expand Down Expand Up @@ -231,7 +247,19 @@ This plugin is distributed under the GPL-2.0-or-later license. See the [LICENSE]

## 📝 Changelog

See [readme.txt](readme.txt) for version history.
See [readme.txt](readme.txt) for the full version history. Recent highlights:

- **1.0.2**
- Filter `blockparty_modal_trigger_allowed_blocks` to control which blocks can be modal triggers; dialog margin and InnerBlocks fixes.
- Crawl Modal blocks from patterns
- Set min required PHP version to 8.0
- Style issues

- **1.0.1**
- Filter `blockparty_modal_trigger_allowed_blocks` to control which blocks can be modal triggers; dialog margin and InnerBlocks fixes.

- **1.0.0**
- Initial release (Modal block, trigger linking, close behaviour, i18n).

---

Expand Down
69 changes: 62 additions & 7 deletions blockparty-modal.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
/**
* Plugin Name: Blockparty Modal
* Description: Modal block for WordPress editor.
* Version: 1.0.1
* Version: 1.0.2
* Requires at least: 6.8
* Requires PHP: 7.4
* Requires PHP: 8.0
* Author: Be API Technical Team
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
Expand All @@ -19,10 +19,17 @@
exit; // Exit if accessed directly.
}

define( 'BLOCKPARTY_MODAL_VERSION', '1.0.1' );
define( 'BLOCKPARTY_MODAL_VERSION', '1.0.2' );
define( 'BLOCKPARTY_MODAL_URL', plugin_dir_url( __FILE__ ) );
define( 'BLOCKPARTY_MODAL_DIR', plugin_dir_path( __FILE__ ) );


// Require vendor
if ( file_exists( BLOCKPARTY_MODAL_DIR . '/vendor/autoload.php' ) ) {
/** @psalm-suppress UnresolvableInclude */
require BLOCKPARTY_MODAL_DIR . '/vendor/autoload.php';
}

/**
* Registers the block(s) metadata from the `blocks-manifest.php` and registers the block type(s)
* based on the registered block metadata. Behind the scenes, it registers also all assets so they can be enqueued
Expand All @@ -36,18 +43,51 @@ function init(): void {
wp_register_block_types_from_metadata_collection( __DIR__ . '/build', __DIR__ . '/build/blocks-manifest.php' );
wp_set_script_translations( 'blockparty-modal-editor-script', 'blockparty-modal', BLOCKPARTY_MODAL_DIR . '/languages' );
}

add_action( 'init', __NAMESPACE__ . '\\init', 10, 0 );

/**
* Passes the list of blocks allowed as modal triggers to the block editor settings
* so the "Attached modal" panel is only shown for those blocks.
*
* @param array<array-key, mixed> $settings Block editor settings.
* @param \WP_Block_Editor_Context $_context Block editor context (unused).
* @return array<array-key, mixed> Modified settings.
*/
function block_editor_settings_modal_trigger_blocks( array $settings, \WP_Block_Editor_Context $_context ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- Required by block_editor_settings_all filter signature.
/** @psalm-suppress MixedAssignment */
$raw = apply_filters(
'blockparty_modal_trigger_allowed_blocks',
get_default_modal_trigger_allowed_blocks()
);
$settings['blockpartyModalTriggerAllowedBlocks'] = array_values(
array_filter( is_array( $raw ) ? $raw : [], 'is_string' )
);
return $settings;
}

add_filter( 'block_editor_settings_all', __NAMESPACE__ . '\\block_editor_settings_modal_trigger_blocks', 10, 2 );

/**
* Default list of block names allowed to be used as modal triggers (linkedModalId).
*
* @return string[] Block names (e.g. 'core/button').
*/
function get_default_modal_trigger_allowed_blocks(): array {
return [ 'core/button' ];
}

/**
* Wraps block output with a trigger wrapper when linkedModalId is set,
* so the view script can open the modal on click.
* Only blocks in the allowed list (filterable) get this behavior; by default only core/button.
* For core/button, the inner link or button is turned into the trigger (no wrapper).
*
* @param string $block_content The block content.
* @param array $block The full block, including attributes.
* @param string $block_content The block content.
* @param array<array-key, mixed> $block The full block, including attributes.
* @return string Filtered block content.
*/
function render_block_add_modal_trigger( $block_content, $block ) {
function render_block_add_modal_trigger( $block_content, array $block ) {
$linked_modal_id = isset( $block['attrs']['linkedModalId'] )
? $block['attrs']['linkedModalId']
: '';
Expand All @@ -56,9 +96,24 @@ function render_block_add_modal_trigger( $block_content, $block ) {
return $block_content;
}

$block_name = (string) ( $block['blockName'] ?? '' );
/** @psalm-suppress MixedAssignment */
$raw_blocks = apply_filters(
'blockparty_modal_trigger_allowed_blocks',
get_default_modal_trigger_allowed_blocks()
);
$allowed_blocks = array_filter(
is_array( $raw_blocks ) ? $raw_blocks : array(),
'is_string'
);

if ( '' === $block_name || ! in_array( $block_name, $allowed_blocks, true ) ) {
return $block_content;
}

$dialog_id = 'modal-' . $linked_modal_id;

if ( isset( $block['blockName'] ) && 'core/button' === $block['blockName'] ) {
if ( 'core/button' === $block_name ) {
$modified = modify_button_block_for_modal_trigger( $block_content, $linked_modal_id, $dialog_id );
if ( null !== $modified ) {
return $modified;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "blockparty-modal",
"version": "1.0.1",
"version": "1.0.2",
"description": "Add a modal block to the WordPress editor.",
"author": "Be API",
"license": "GPL-2.0-or-later",
Expand Down
6 changes: 6 additions & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ directory take precedence. For example, `/assets/screenshot-1.png` would win ove

== Changelog ==

= 1.0.2 =
* Filter `blockparty_modal_trigger_allowed_blocks` to control which blocks can be modal triggers; dialog margin and InnerBlocks fixes.
* Crawl Modal blocks from patterns
* Set min required PHP version to 8.0
* Style issues

= 1.0.1 =
* Fix margin style for dialog element; set to auto by default instead of 0.
* Remove dupplicated InnerBlocks.Content
Expand Down
2 changes: 1 addition & 1 deletion src/blockparty-modal/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "blockparty/modal",
"version": "1.0.1",
"version": "1.0.2",
"title": "Modal",
"category": "widgets",
"description": "Insert a modal dialog that opens on trigger. Configure content and behaviour in the editor; the modal is displayed on the frontend when activated.",
Expand Down
17 changes: 13 additions & 4 deletions src/blockparty-modal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import metadata from './block.json';
import {
MODAL_BLOCK_NAME,
LINKED_MODAL_ATTR,
getModalOptionsFromBlocks,
getModalOptionsFromEditor,
addLinkedModalAttribute,
} from './utils';

Expand Down Expand Up @@ -52,7 +52,7 @@ blockTypes.forEach( ( blockType ) => {
}
} );

// Add "Open modal on click" panel with Combobox to all blocks except the modal itself.
// Add "Attached modal" panel with Combobox only to blocks allowed as modal triggers (see filter blockparty_modal_trigger_allowed_blocks).
addFilter(
'editor.BlockEdit',
'blockparty-modal/with-modal-trigger-control',
Expand All @@ -63,9 +63,18 @@ addFilter(
return <BlockEdit { ...props } />;
}

const allowedBlocks = useSelect( ( select ) => {
const settings = select( 'core/block-editor' ).getSettings();
const list = settings?.blockpartyModalTriggerAllowedBlocks;
return Array.isArray( list ) ? list : [ 'core/button' ];
}, [] );

if ( ! allowedBlocks.includes( name ) ) {
return <BlockEdit { ...props } />;
}

const modalOptions = useSelect( ( select ) => {
const blocks = select( 'core/block-editor' ).getBlocks();
return getModalOptionsFromBlocks( blocks );
return getModalOptionsFromEditor( select );
}, [] );

const options = [
Expand Down
5 changes: 5 additions & 0 deletions src/blockparty-modal/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@

&:not(.block-editor-block-list__block) {
margin: var(--wp-block-blockparty-modal-margin);

&:first-child,
&:last-child {
margin-block: var(--wp-block-blockparty-modal-margin);
}
}

&::backdrop {
Expand Down
43 changes: 21 additions & 22 deletions src/blockparty-modal/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,33 @@ export function generateStableModalId() {
}

/**
* Recursively collect blockparty/modal blocks with modalId and title.
* Collect modal options from the block editor store using getClientIdsWithDescendants
* and getBlock, so modals inside reusable blocks (core/block) and patterns are included.
*
* @param {Object[]} blocks - Block list.
* @param {Function} select - The wp.data select function (e.g. from useSelect).
* @return {Object[]} Options for ComboboxControl.
*/
export function getModalOptionsFromBlocks( blocks ) {
export function getModalOptionsFromEditor( select ) {
const blockEditor = select( 'core/block-editor' );
const clientIds = blockEditor.getClientIdsWithDescendants();
if ( ! Array.isArray( clientIds ) ) {
return [];
}
const options = [];
function traverse( blockList ) {
if ( ! blockList || ! blockList.length ) {
return;
}
for ( const block of blockList ) {
if ( block.name === MODAL_BLOCK_NAME ) {
const modalId = block.attributes?.modalId || block.clientId;
const title =
block.attributes?.title?.trim() ||
__( 'Modal', 'blockparty-modal' );
options.push( {
value: modalId,
label: title || `#${ modalId.slice( 0, 8 ) }`,
} );
}
if ( block.innerBlocks?.length ) {
traverse( block.innerBlocks );
}
for ( const clientId of clientIds ) {
const block = blockEditor.getBlock( clientId );
if ( ! block || block.name !== MODAL_BLOCK_NAME ) {
continue;
}
const modalId = block.attributes?.modalId || block.clientId;
const title =
block.attributes?.title?.trim() ||
__( 'Modal', 'blockparty-modal' );
options.push( {
value: modalId,
label: title || `#${ String( modalId ).slice( 0, 8 ) }`,
} );
}
traverse( blocks );
return options;
}

Expand Down
Loading