diff --git a/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php b/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php
index 7b1bc6513977b..1f53ca1331a37 100644
--- a/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php
+++ b/packages/e2e-tests/plugins/interactive-blocks/tovdom-islands/render.php
@@ -68,4 +68,16 @@
+
+
+
+
+
+
+
+ The directive above should keep the `tovdom-island` namespace,
+ so this message should not be visible.
+
+
+
diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md
index ce90835dda23d..8c03cfc314efe 100644
--- a/packages/interactivity/CHANGELOG.md
+++ b/packages/interactivity/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### Bug Fix
+
+- Fix namespaces when there are nested interactive regions. ([#57029](https://github.com/WordPress/gutenberg/pull/57029))
+
## 3.1.0 (2023-12-13)
## 3.0.0 (2023-11-29)
diff --git a/packages/interactivity/src/vdom.js b/packages/interactivity/src/vdom.js
index b1342ac271a8e..860a3149e6ffd 100644
--- a/packages/interactivity/src/vdom.js
+++ b/packages/interactivity/src/vdom.js
@@ -10,7 +10,8 @@ import { directivePrefix as p } from './constants';
const ignoreAttr = `data-${ p }-ignore`;
const islandAttr = `data-${ p }-interactive`;
const fullPrefix = `data-${ p }-`;
-let namespace = null;
+const namespaces = [];
+const currentNamespace = () => namespaces[ namespaces.length - 1 ] ?? null;
// Regular expression for directive parsing.
const directiveParser = new RegExp(
@@ -79,7 +80,7 @@ export function toVdom( root ) {
} catch ( e ) {}
if ( n === islandAttr ) {
island = true;
- namespace = value?.namespace ?? null;
+ namespaces.push( value?.namespace ?? null );
} else {
directives.push( [ n, ns, value ] );
}
@@ -107,7 +108,7 @@ export function toVdom( root ) {
directiveParser.exec( name );
if ( ! obj[ prefix ] ) obj[ prefix ] = [];
obj[ prefix ].push( {
- namespace: ns ?? namespace,
+ namespace: ns ?? currentNamespace(),
value,
suffix,
} );
@@ -127,6 +128,9 @@ export function toVdom( root ) {
treeWalker.parentNode();
}
+ // Restore previous namespace.
+ if ( island ) namespaces.pop();
+
return [ h( node.localName, props, children ) ];
}
diff --git a/test/e2e/specs/interactivity/tovdom-islands.spec.ts b/test/e2e/specs/interactivity/tovdom-islands.spec.ts
index fcc7c6081077a..257b0a0fc94b8 100644
--- a/test/e2e/specs/interactivity/tovdom-islands.spec.ts
+++ b/test/e2e/specs/interactivity/tovdom-islands.spec.ts
@@ -55,4 +55,11 @@ test.describe( 'toVdom - islands', () => {
);
await expect( el ).toBeHidden();
} );
+
+ test( 'islands should recover their namespace if an inner island has changed it', async ( {
+ page,
+ } ) => {
+ const el = page.getByTestId( 'directive after different namespace' );
+ await expect( el ).toBeHidden();
+ } );
} );