Skip to content

Conversation

@sethrubenstein
Copy link
Contributor

@sethrubenstein sethrubenstein commented Sep 11, 2025

What?

Closes #61297

#61297

This PR introduces 3 blocks from prc-block-library.

core/dialog

core/dialog is actually a collection of three blocks: 1. core/dialog a wrapper really for 2. core/dialog-trigger, and 3. core/dialog-element.

core/dialog-trigger

This block is the "trigger" or "action" to open the dialog-element <dialog/> block.

core/dialog-element

This is the primary block and is a representation of the <dialog/> element in modal form. With added support for positioning center, top left, top center, top right, center left, center right, bottom left, center, and right and support for backdrop coloring it utilize core supports for box shadow and border quite well.

Additionally, this block and it's iAPI store have been designed to be maximally extensible. We utilize this block throughout pewresearch.org, most complexly in RLS https://www.pewresearch.org/religious-landscape-study/age-distribution/18-29/?dialogId=dialog_prayer-frequency&activeChartId=6ac4c46314ff76e21be70e1f66fe1a19. This link shows off another feature built into core/dialog, in this case a modified version of it's built in Deep Linking feature. Which allows you to open a dialog on page render so long as it's id is in the url with ?dialogId. There is also an Auto Activation timer for opening a dialog immediately on page render based on a ms timer. Lastly, other plugins and consumers of this block can open/close or close all dialogs utilizing the Interactivity API, like so: store('core/dialog').state.dialogs.[{the dialog's elm id}].isOpen = true or of course identifying all dialogs on page via store('core/dialog').state.dialogs.

Lastly, there is a block binding provided to associate a heading with the dialogLabel attribute for core/dialog-element.
This provides an easy and familiar interface to provide a standard paradigm ~ a heading at the top of a dialog while also allowing the user to set the label and remove the display block, the heading, from the dialog. In either case, if a heading is not present a dynamic heading is created for accessibility and hidden from view on the frontend.

Why?

Dialogs, or "modals" are a common UI pattern that many 3rd parties have attempted. This provides a base and a representation of an actual HTML element, <dialog/> that should be present in the core block library.

How?

Testing Instructions

  1. Enable experimental blocks from Gutenberg settings.
  2. Open a page/post and insert a Dialog Block
  3. Add content to the "trigger"
  4. Click "edit dialog" in the block toolbar
  5. The dialog should open in the editor
  6. Add some content and save the post
  7. Preview post and click on your trigger
  8. Your dialog should appear

Testing Instructions for Keyboard

The block toolbar controls should allow keyboard accessibility to open/close dialogs in the editor. And on the frontend the trigger is wrapped with a button element with proper arias to signal relationships. In both contexts there are keyboard handlers to handle escaping out of a dialog.

Screenshots or screencast

CleanShot.2025-09-11.at.13.24.28.mp4
CleanShot.2025-09-11.at.13.29.24.mp4

@github-actions
Copy link

github-actions bot commented Sep 11, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: sethrubenstein <smrubenstein@git.wordpress.org>
Co-authored-by: luisherranz <luisherranz@git.wordpress.org>
Co-authored-by: paaljoachim <paaljoachim@git.wordpress.org>
Co-authored-by: michalczaplinski <czapla@git.wordpress.org>
Co-authored-by: draganescu <andraganescu@git.wordpress.org>
Co-authored-by: djcowan <djcowan@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: TheJeffr0 <jeffr0@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: scruffian <scruffian@git.wordpress.org>
Co-authored-by: vk17-starlord <vineet2003@git.wordpress.org>
Co-authored-by: bhubbard <bhubbard@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@sethrubenstein sethrubenstein changed the title Core/dialog New Block: core/dialog Sep 11, 2025
@sethrubenstein
Copy link
Contributor Author

Just occurred to me, this will need block icons for all three blocks

@Mamaduka Mamaduka added the New Block Suggestion for a new block label Sep 12, 2025
Copy link
Member

@luisherranz luisherranz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome, Seth 🙂👏

Lastly, other plugins and consumers of this block can open/close or close all dialogs utilizing the Interactivity API, like so: store('core/dialog').state.dialogs.[{the dialog's elm id}].isOpen = true or of course identifying all dialogs on page via store('core/dialog').state.dialogs.

We can combine private and public stores to make this block extensible without exposing all the internal details of the store publicly. It would be the first one from Core, but I think it would be good to do it so we can start exploring extensibility patterns. We can check it a bit later when the new implementation has advanced a bit more.

Other than that, I've only taken a preliminary look at the Interactivity API part, and in general, it's quite good, although here are a few small suggestions.

Co-authored-by: Luis Herranz <luisherranz@gmail.com>
@sethrubenstein
Copy link
Contributor Author

I'll be making a few more updates by end of the week, mostly around animations and your suggestions @luisherranz.

@sethrubenstein
Copy link
Contributor Author

sethrubenstein commented Sep 16, 2025

We can combine private and public stores to make this block extensible without exposing all the internal details of the store publicly. It would be the first one from Core, but I think it would be good to do it so we can start exploring extensibility patterns. We can check it a bit later when the new implementation has advanced a bit more.

@luisherranz on this topic, interesting, I would just do like this?

// 1st party
store('core/dialog', {
	state:{... internal derivced state funcs },
	actions: {... internal funcs },
	callbacks: {... internal callbacks }
}, {lock:true});

// Accessible by 3rd parties
const { state } = store('core/dialog', {
	actions:{
		open(id){
			state.dialogs[id].isOpen = true;
		}
	}
}, {lock:false})

@luisherranz
Copy link
Member

luisherranz commented Sep 17, 2025

You need to use a different namespace. So it would be more like this:

const { state, actions } = store( 'core/dialog/private', {
  state: {
    // internal derived state funcs
  },
  actions: {
    // internal funcs
  },
  callbacks: {
    // internal callbacks
  }
}, {
  lock: true
} );

store( 'core/dialog', {
  // We add here the state that we want to be public.
  state: {
    // We can use getters for the state that we want to be read-only.
    get dialog() {
      return state.dialog;
    },
    // We can use setters for the state that we want to be modified.
    get isOpen() {
      return state.dialog.isOpen;
    },
    set isOpen( value ) {
      state.dialog.isOpen = value;
    },
  },
  // We add here the actions that we want to be public.
  actions: {
    open( id ) {
      actions.open( id );
    }
  }
} );

I've given the example of how to modify the private state if necessary, but for the most part, we're going to want the state to be read-only and to be modified through actions on public stores.

EDIT: Oh, to return objects like state.dialog that we don't want to be modified, we have to make a small proxy wrapper. If we find that this is a common pattern, we can add this utility to the Interactivity API itself.

function createReadOnlyProxy( obj ) {
  return new Proxy( obj, {
    get( target, prop ) {
      const value = target[ prop ];
      if ( typeof value === 'object' && value !== null ) {
        return createReadOnlyProxy( value );
      }
      return value;
    },
    set() {
      return false;
    },
    deleteProperty() {
      return false;
    }
  });
}

store( 'core/dialog', {
  state: {
    get dialog() {
      return createReadOnlyProxy( state.dialog );
    },

@sethrubenstein
Copy link
Contributor Author

Oh one other thing, I think I'm going to abstract out the dialog close button into it's own block so it can be more easily changed by 3rd parties.

@sethrubenstein
Copy link
Contributor Author

Actually, on further thought, I think I'm going to try to adopt core/icon #71227 in some way for the close button. Stay tuned.

@sethrubenstein
Copy link
Contributor Author

sethrubenstein commented Sep 25, 2025

Some updates from me:

  1. Implemented @luisherranz review on iAPI store including private instance. During the review phase I've also implemented a simple WP_DEBUG conditional button on the dialog trigger that tests 3rd party interactions with this new store method, I'll pull that after a little additional testing.
  2. I have changed how id's are handled, slightly. Instead of another attribute and another ui control I've tied the ID to the parent dialog block's anchor id. If one is not set we'll generate a unique id dynamically and add it to the element and into block context so the child blocks can utilize it accordingly.
  3. Will look into bringing in core/icon for the close button next week.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I wasn't sure about (although I've found it critical to a good experience in the editor) is introducing a custom redux store for a block. I don't believe I've seen a core block do this before. Any thoughts, concerns against this pattern emerging?

@sethrubenstein
Copy link
Contributor Author

Once we're done with #69789 I'll turn my attention back to this one and finding alignment with #71227

@priethor priethor added the [Type] Feature New feature to highlight in changelogs. label Oct 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

New Block Suggestion for a new block [Type] Feature New feature to highlight in changelogs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New Block: Dialog Popup

4 participants