Date: Fri, 25 Aug 2023 20:21:32 +0200
Subject: [PATCH 10/14] Copy current implementation
---
packages/interactivity/src/directives.js | 52 ++++++++++++++++++++++++
packages/interactivity/src/slots.js | 38 +++++++++++++++++
2 files changed, 90 insertions(+)
create mode 100644 packages/interactivity/src/slots.js
diff --git a/packages/interactivity/src/directives.js b/packages/interactivity/src/directives.js
index 0fd532debc775..bc54c57b8d43b 100644
--- a/packages/interactivity/src/directives.js
+++ b/packages/interactivity/src/directives.js
@@ -10,6 +10,7 @@ import { deepSignal, peek } from 'deepsignal';
import { createPortal } from './portals';
import { useSignalEffect } from './utils';
import { directive } from './hooks';
+import { SlotProvider, Slot, Fill } from './slots';
const isObject = ( item ) =>
item && typeof item === 'object' && ! Array.isArray( item );
@@ -305,4 +306,55 @@ export default () => {
} );
}
);
+
+ // data-wp-slot
+ directive(
+ 'slot',
+ ( {
+ directives: {
+ slot: { default: slot, above, below },
+ },
+ props: { children },
+ } ) => {
+ return (
+ <>
+ { above &&
}
+ { slot ? (
+
{ children }
+ ) : (
+ children
+ ) }
+ { below &&
}
+ >
+ );
+ },
+ { priority: 4 }
+ );
+
+ // data-wp-fill
+ directive(
+ 'fill',
+ ( {
+ directives: {
+ fill: { default: fill },
+ },
+ props: { children },
+ evaluate,
+ context,
+ } ) => {
+ const contextValue = useContext( context );
+ const slot = evaluate( fill, { context: contextValue } );
+ return
{ children };
+ },
+ { priority: 4 }
+ );
+
+ // data-wp-slot-provider
+ directive(
+ 'slot-provider',
+ ( { props: { children } } ) => (
+
{ children }
+ ),
+ { priority: 4 }
+ );
};
diff --git a/packages/interactivity/src/slots.js b/packages/interactivity/src/slots.js
new file mode 100644
index 0000000000000..e8bc6ddfa368f
--- /dev/null
+++ b/packages/interactivity/src/slots.js
@@ -0,0 +1,38 @@
+/**
+ * External dependencies
+ */
+import { createContext } from 'preact';
+import { useContext, useEffect } from 'preact/hooks';
+import { signal } from '@preact/signals';
+
+const slotsContext = createContext();
+
+export const Fill = ( { slot, children } ) => {
+ const slots = useContext( slotsContext );
+
+ useEffect( () => {
+ if ( slot ) {
+ slots.value = { ...slots.value, [ slot ]: children };
+ return () => {
+ slots.value = { ...slots.value, [ slot ]: null };
+ };
+ }
+ }, [ slots, slot, children ] );
+
+ return !! slot ? null : children;
+};
+
+export const SlotProvider = ( { children } ) => {
+ return (
+ // TODO: We can change this to use deepsignal once this PR is merged.
+ // https://github.com/luisherranz/deepsignal/pull/38
+
+ { children }
+
+ );
+};
+
+export const Slot = ( { name, children } ) => {
+ const slots = useContext( slotsContext );
+ return slots.value[ name ] || children;
+};
From 72eba45e9065f41aa879fae0dc650ad363b15474 Mon Sep 17 00:00:00 2001
From: David Arenas
Date: Fri, 25 Aug 2023 22:19:19 +0200
Subject: [PATCH 11/14] Refactor data-wp-slot
---
packages/interactivity/src/directives.js | 41 +++++++++++++++++-------
1 file changed, 29 insertions(+), 12 deletions(-)
diff --git a/packages/interactivity/src/directives.js b/packages/interactivity/src/directives.js
index bc54c57b8d43b..f0a3d7e32e09e 100644
--- a/packages/interactivity/src/directives.js
+++ b/packages/interactivity/src/directives.js
@@ -312,21 +312,38 @@ export default () => {
'slot',
( {
directives: {
- slot: { default: slot, above, below },
+ slot: { default: slot },
},
props: { children },
+ element,
} ) => {
- return (
- <>
- { above && }
- { slot ? (
- { children }
- ) : (
- children
- ) }
- { below && }
- >
- );
+ const name = typeof slot === 'string' ? slot : slot.name;
+ const position = slot.position || 'children';
+
+ if ( position === 'before' ) {
+ return (
+ <>
+
+ { children }
+ >
+ );
+ }
+ if ( position === 'after' ) {
+ return (
+ <>
+ { children }
+
+ >
+ );
+ }
+ if ( position === 'replace' ) {
+ return { children };
+ }
+ if ( position === 'children' ) {
+ element.props.children = (
+ { element.props.children }
+ );
+ }
},
{ priority: 4 }
);
From 0f743296e464799711f80d7498865084e1463459 Mon Sep 17 00:00:00 2001
From: David Arenas
Date: Fri, 25 Aug 2023 22:19:38 +0200
Subject: [PATCH 12/14] Add slot and fill tests
---
.../directive-slots/block.json | 14 ++
.../directive-slots/render.php | 67 +++++++
.../directive-slots/view.js | 18 ++
.../interactivity/directive-slots.spec.ts | 186 ++++++++++++++++++
4 files changed, 285 insertions(+)
create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-slots/block.json
create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-slots/render.php
create mode 100644 packages/e2e-tests/plugins/interactive-blocks/directive-slots/view.js
create mode 100644 test/e2e/specs/interactivity/directive-slots.spec.ts
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-slots/block.json b/packages/e2e-tests/plugins/interactive-blocks/directive-slots/block.json
new file mode 100644
index 0000000000000..f79f89a6e81b8
--- /dev/null
+++ b/packages/e2e-tests/plugins/interactive-blocks/directive-slots/block.json
@@ -0,0 +1,14 @@
+{
+ "apiVersion": 2,
+ "name": "test/directive-slots",
+ "title": "E2E Interactivity tests - directive slots",
+ "category": "text",
+ "icon": "heart",
+ "description": "",
+ "supports": {
+ "interactivity": true
+ },
+ "textdomain": "e2e-interactivity",
+ "viewScript": "directive-slots-view",
+ "render": "file:./render.php"
+}
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-slots/render.php b/packages/e2e-tests/plugins/interactive-blocks/directive-slots/render.php
new file mode 100644
index 0000000000000..5c1558d35403d
--- /dev/null
+++ b/packages/e2e-tests/plugins/interactive-blocks/directive-slots/render.php
@@ -0,0 +1,67 @@
+
+
+
+
[1]
+
[2]
+
[3]
+
[4]
+
[5]
+
+
+
+ initial
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/e2e-tests/plugins/interactive-blocks/directive-slots/view.js b/packages/e2e-tests/plugins/interactive-blocks/directive-slots/view.js
new file mode 100644
index 0000000000000..ab5b39379f3a8
--- /dev/null
+++ b/packages/e2e-tests/plugins/interactive-blocks/directive-slots/view.js
@@ -0,0 +1,18 @@
+( ( { wp } ) => {
+ const { store } = wp.interactivity;
+
+ store( {
+ state: {
+ slot: ''
+ },
+ actions: {
+ changeSlot: ( { state, event } ) => {
+ state.slot = event.target.dataset.slot;
+ },
+ updateSlotText: ( { context } ) => {
+ const n = context.text[1];
+ context.text = `[${n} updated]`;
+ },
+ },
+ } );
+} )( window );
diff --git a/test/e2e/specs/interactivity/directive-slots.spec.ts b/test/e2e/specs/interactivity/directive-slots.spec.ts
new file mode 100644
index 0000000000000..d93e50f767215
--- /dev/null
+++ b/test/e2e/specs/interactivity/directive-slots.spec.ts
@@ -0,0 +1,186 @@
+/**
+ * Internal dependencies
+ */
+import { test, expect } from './fixtures';
+
+test.describe( 'data-wp-slot', () => {
+ test.beforeAll( async ( { interactivityUtils: utils } ) => {
+ await utils.activatePlugins();
+ await utils.addPostWithBlock( 'test/directive-slots' );
+ } );
+
+ test.beforeEach( async ( { interactivityUtils: utils, page } ) => {
+ await page.goto( utils.getLink( 'test/directive-slots' ) );
+ } );
+
+ test.afterAll( async ( { interactivityUtils: utils } ) => {
+ await utils.deactivatePlugins();
+ await utils.deleteAllPosts();
+ } );
+
+ test( 'should render the fill in its children by default', async ( {
+ page,
+ } ) => {
+ const slot1 = page.getByTestId( 'slot-1' );
+ const slots = page.getByTestId( 'slots' );
+ const fillContainer = page.getByTestId( 'fill-container' );
+
+ await page.getByTestId( 'slot-1-button' ).click();
+
+ await expect( fillContainer ).toBeEmpty();
+ await expect( slot1.getByTestId( 'fill' ) ).toBeVisible();
+ await expect( slot1 ).toHaveText( 'fill inside slot 1' );
+ await expect( slots.locator( 'css= > *' ) ).toHaveText( [
+ 'fill inside slot 1',
+ '[2]',
+ '[3]',
+ '[4]',
+ '[5]',
+ ] );
+ } );
+
+ test( 'should render the fill before if specified', async ( { page } ) => {
+ const slot2 = page.getByTestId( 'slot-2' );
+ const slots = page.getByTestId( 'slots' );
+ const fillContainer = page.getByTestId( 'fill-container' );
+
+ await page.getByTestId( 'slot-2-button' ).click();
+
+ await expect( fillContainer ).toBeEmpty();
+ await expect( slot2 ).toHaveText( '[2]' );
+ await expect( slots.getByTestId( 'fill' ) ).toBeVisible();
+ await expect( slots.locator( 'css= > *' ) ).toHaveText( [
+ '[1]',
+ 'fill inside slots',
+ '[2]',
+ '[3]',
+ '[4]',
+ '[5]',
+ ] );
+ } );
+
+ test( 'should render the fill after if specified', async ( { page } ) => {
+ const slot3 = page.getByTestId( 'slot-3' );
+ const slots = page.getByTestId( 'slots' );
+ const fillContainer = page.getByTestId( 'fill-container' );
+
+ await page.getByTestId( 'slot-3-button' ).click();
+
+ await expect( fillContainer ).toBeEmpty();
+ await expect( slot3 ).toHaveText( '[3]' );
+ await expect( slots.getByTestId( 'fill' ) ).toBeVisible();
+ await expect( slots.locator( 'css= > *' ) ).toHaveText( [
+ '[1]',
+ '[2]',
+ '[3]',
+ 'fill inside slots',
+ '[4]',
+ '[5]',
+ ] );
+ } );
+
+ test( 'should render the fill in its children if specified', async ( {
+ page,
+ } ) => {
+ const slot4 = page.getByTestId( 'slot-4' );
+ const slots = page.getByTestId( 'slots' );
+ const fillContainer = page.getByTestId( 'fill-container' );
+
+ await page.getByTestId( 'slot-4-button' ).click();
+
+ await expect( fillContainer ).toBeEmpty();
+ await expect( slot4.getByTestId( 'fill' ) ).toBeVisible();
+ await expect( slot4 ).toHaveText( 'fill inside slot 4' );
+ await expect( slots.locator( 'css= > *' ) ).toHaveText( [
+ '[1]',
+ '[2]',
+ '[3]',
+ 'fill inside slot 4',
+ '[5]',
+ ] );
+ } );
+
+ test( 'should be replaced by the fill if specified', async ( { page } ) => {
+ const slot5 = page.getByTestId( 'slot-5' );
+ const slots = page.getByTestId( 'slots' );
+ const fillContainer = page.getByTestId( 'fill-container' );
+
+ await page.getByTestId( 'slot-5-button' ).click();
+
+ await expect( fillContainer ).toBeEmpty();
+ await expect( slot5 ).not.toBeVisible();
+ await expect( slots.getByTestId( 'fill' ) ).toBeVisible();
+ await expect( slots.locator( 'css= > *' ) ).toHaveText( [
+ '[1]',
+ '[2]',
+ '[3]',
+ '[4]',
+ 'fill inside slots',
+ ] );
+ } );
+
+ test( 'should keep the fill in its original position if no slot matches', async ( {
+ page,
+ } ) => {
+ const fillContainer = page.getByTestId( 'fill-container' );
+ await expect( fillContainer.getByTestId( 'fill' ) ).toBeVisible();
+
+ await page.getByTestId( 'slot-1-button' ).click();
+
+ await expect( fillContainer ).toBeEmpty();
+
+ await page.getByTestId( 'reset' ).click();
+
+ await expect( fillContainer.getByTestId( 'fill' ) ).toBeVisible();
+ } );
+
+ test( 'should not be re-mounted when adding the fill before', async ( {
+ page,
+ } ) => {
+ const slot2 = page.getByTestId( 'slot-2' );
+ const slots = page.getByTestId( 'slots' );
+
+ await expect( slot2 ).toHaveText( '[2]' );
+
+ await slot2.click();
+
+ await expect( slot2 ).toHaveText( '[2 updated]' );
+
+ await page.getByTestId( 'slot-2-button' ).click();
+
+ await expect( slots.getByTestId( 'fill' ) ).toBeVisible();
+ await expect( slots.locator( 'css= > *' ) ).toHaveText( [
+ '[1]',
+ 'fill inside slots',
+ '[2 updated]',
+ '[3]',
+ '[4]',
+ '[5]',
+ ] );
+ } );
+
+ test( 'should not be re-mounted when adding the fill after', async ( {
+ page,
+ } ) => {
+ const slot3 = page.getByTestId( 'slot-3' );
+ const slots = page.getByTestId( 'slots' );
+
+ await expect( slot3 ).toHaveText( '[3]' );
+
+ await slot3.click();
+
+ await expect( slot3 ).toHaveText( '[3 updated]' );
+
+ await page.getByTestId( 'slot-3-button' ).click();
+
+ await expect( slots.getByTestId( 'fill' ) ).toBeVisible();
+ await expect( slots.locator( 'css= > *' ) ).toHaveText( [
+ '[1]',
+ '[2]',
+ '[3 updated]',
+ 'fill inside slots',
+ '[4]',
+ '[5]',
+ ] );
+ } );
+} );
From 0013244a54b54503b7d71d545e15dc53ec991315 Mon Sep 17 00:00:00 2001
From: David Arenas
Date: Fri, 25 Aug 2023 22:41:55 +0200
Subject: [PATCH 13/14] Add changelog
---
packages/interactivity/CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md
index 62515115484fa..9ce48303637d8 100644
--- a/packages/interactivity/CHANGELOG.md
+++ b/packages/interactivity/CHANGELOG.md
@@ -15,6 +15,8 @@
- Allow passing optional `afterLoad` callbacks to `store` calls. ([#53363](https://github.com/WordPress/gutenberg/pull/53363))
+- Add new directives that implement the Slot and Fill pattern: `data-wp-slot-provider`, `data-wp-slot` and `data-wp-fill`. ([#53958](https://github.com/WordPress/gutenberg/pull/53958))
+
### Bug Fix
- Add support for underscores and leading dashes in the suffix part of the directive. ([#53337](https://github.com/WordPress/gutenberg/pull/53337))
From cb8eacb437e2346949c93b05d8695553f16c8952 Mon Sep 17 00:00:00 2001
From: David Arenas
Date: Tue, 29 Aug 2023 01:52:28 +0200
Subject: [PATCH 14/14] Fix changelog
---
packages/interactivity/CHANGELOG.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md
index 9ce48303637d8..6a4565f3ad183 100644
--- a/packages/interactivity/CHANGELOG.md
+++ b/packages/interactivity/CHANGELOG.md
@@ -9,14 +9,16 @@
- Support region-based client-side navigation. ([#53733](https://github.com/WordPress/gutenberg/pull/53733))
- Improve `data-wp-bind` hydration to match Preact's logic. ([#54003](https://github.com/WordPress/gutenberg/pull/54003))
+### New Features
+
+- Add new directives that implement the Slot and Fill pattern: `data-wp-slot-provider`, `data-wp-slot` and `data-wp-fill`. ([#53958](https://github.com/WordPress/gutenberg/pull/53958))
+
## 2.1.0 (2023-08-16)
### New Features
- Allow passing optional `afterLoad` callbacks to `store` calls. ([#53363](https://github.com/WordPress/gutenberg/pull/53363))
-- Add new directives that implement the Slot and Fill pattern: `data-wp-slot-provider`, `data-wp-slot` and `data-wp-fill`. ([#53958](https://github.com/WordPress/gutenberg/pull/53958))
-
### Bug Fix
- Add support for underscores and leading dashes in the suffix part of the directive. ([#53337](https://github.com/WordPress/gutenberg/pull/53337))