Skip to content

Commit

Permalink
Create @wordpress/interactivity with the Interactivity API (#50906)
Browse files Browse the repository at this point in the history
* Start with package.json and README

* Add new package to docs/manifest.json

* Copy-paste runtime files from block-library

* Add .npmrc file

* Add interactivity package to dependencies

* Create a custom webpack config for interactivity

* Expose interactivity runtime in `wp.interactivity`

* Update package-lock

* Add `@wordpress/interactivity` to block-library deps

* Rename entry point to index

* Remove vendors chunk

* Add oddly required aliases

* Add view prefix to interactivity.js files

* Use view-interactivity files when enabled

* Stop adding defer to Interactivity scripts

* Remove webpack config for interactivity.js files

* Remove interactivity runtime from block-library

* Remove interactivity runtime from sideEffects

* Undo temporary fix for Interactivity API in dependency-extraction-webpack-plugin

* Remove script loader for Interactivity API runtime

* Remove block-librar/interactivity from build_files

* Add src/index.js to Interactivity API entry file

* Remove unnecessary aliases

* Restore data-wp-body directive

* Interactivity API: add `wp_store` (#51191)

* Add `wp_store` to the Interactivity API

* Rename WP_Interactivity_Store and move filter to scripts file

* Remove todos to change the store id

* Rename syntax to -- and data-wp-interactive (#51241)

* Interactivity API: initial support for SSR (#51229)

* Initial version working with basic support for wp-bind

* Add wp-context

* Add wp-class

* Add wp-style

* Add wp-text

* Add directive processing tests

* Add WP_Directive_Processor class tests

* Add wp-bind tests

* Add wp-context tests

* Add wp-class tests

* Add wp-style tests

* Add wp-text tests

* Add evaluate tests

* Fix PHP lint

* Prevent errors with incorrect JSON objects

* Add support for functions in the server

* Remove require for missing script-loader.php

* Remove missing PHP file

* Rename view file and fix block.json

* Add "interactivity" to supports and fix renaming

* Code improvements for the SSR part of the Interactivity API (#51640)

* Fix multi-line comments and add examples

* Add parse_attribute_name static method to WP_Directive_Processor

* Replace array functions with a foreach loop

* Add explanatory comment for the negation operator check

* Replace $array with $path_segments

* Minor fix for the negation operator comment

* Call only instances of Closure

* Improve negation operator code style

* Do not lower-case tags

* Use static parse_attribute_name inside directive processors

* Add basic error handling in wp-context

* Fix hidden identation errors

* Use the correct variable name

* Fix test for evaluating functions

* Remove references to "attribute" directives

* Remove emtpy lines in multi-line function calls

* Fix typo

---------

Co-authored-by: Luis Herranz <luisherranz@gmail.com>

* Add the full Interactivity API runtime (but removing the client-side navigation). (#51194)

* Add show and text directives

* Move directive bind tests

* Move the rest of e2e tests (except csn-related)

* Add interactive-blocks plugin for e2e tests

* Move test plugins one folder up

* Add plugin to .wp-env.json

* Change directive-bind spec file to use new plugin

* Move plugin to e2e-tests package

* Move HTML for directive-bind to plugin

* Update exposed properties from preact

* Refactor directive-bind spec file

* Create directive-effect block for e2e testing

* Update directive-effect spec file

* Remove unnecessary files

* Fix e2e tests for bind and effect directives

* Refactor fixtures and use them for bind and effect

* Remove unnecessary editorScript

* Fix e2e test for directive priorities

* Remove unnecessary files

* Fix negation operator

* Refactor store-tag e2e tests

* Refactor directive-class e2e tests

* Remove extra spaces

* Add util for removing all created posts

* Add block for context directive

* Add block for directive show testing

* Remove unintentionally added artifact

* Ignore artifacts generated inside /test/e2e

* Remove unused html

* Add block for directive text testing

* Add blocks for tovdom testing

* Update directives syntax in e2e tests

* Add getLink to InteractivityUtils

* Fix php lint errors

* Add disable_directives_ssr param

* Fix phpcs errors

* Fix missing phpcs error and warnings

* Remove `wp-interactivity` from `viewScript`

---------

Co-authored-by: Luis Herranz <luisherranz@gmail.com>

* Remove custom watchOptions in interactivity webpack config

* Update version and description of interactivity package

---------

Co-authored-by: Luis Herranz <luisherranz@gmail.com>
  • Loading branch information
DAreRodz and luisherranz authored Jun 28, 2023
1 parent f4ed553 commit 9b8d5c1
Show file tree
Hide file tree
Showing 106 changed files with 3,983 additions and 532 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ coverage
*.log
yarn.lock
/artifacts
/test/e2e/artifacts
/perf-envs
/composer.lock

Expand Down
1 change: 0 additions & 1 deletion bin/build-plugin-zip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ build_files=$(
build/block-library/blocks/*.php \
build/block-library/blocks/*/block.json \
build/block-library/blocks/*/*.{js,js.map,css,asset.php} \
build/block-library/interactivity/*.{js,js.map,asset.php} \
build/edit-widgets/blocks/*/block.json \
build/widgets/blocks/*.php \
build/widgets/blocks/*/block.json \
Expand Down
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,12 @@
"markdown_source": "../packages/icons/README.md",
"parent": "packages"
},
{
"title": "@wordpress/interactivity",
"slug": "packages-interactivity",
"markdown_source": "../packages/interactivity/README.md",
"parent": "packages"
},
{
"title": "@wordpress/interface",
"slug": "packages-interface",
Expand Down
4 changes: 2 additions & 2 deletions docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ Add a link to a downloadable file. ([Source](https://github.com/WordPress/gutenb

- **Name:** core/file
- **Category:** media
- **Supports:** align, anchor, color (background, gradients, link, ~~text~~)
- **Supports:** align, anchor, color (background, gradients, link, ~~text~~), interactivity
- **Attributes:** displayPreview, downloadButtonText, fileId, fileName, href, id, previewHeight, showDownloadButton, textLinkHref, textLinkTarget

## Footnotes
Expand Down Expand Up @@ -421,7 +421,7 @@ A collection of blocks that allow visitors to get around your site. ([Source](ht

- **Name:** core/navigation
- **Category:** theme
- **Supports:** align (full, wide), inserter, layout (allowSizingOnChildren, default, ~~allowInheriting~~, ~~allowSwitching~~, ~~allowVerticalAlignment~~), spacing (blockGap, units), typography (fontSize, lineHeight), ~~html~~
- **Supports:** align (full, wide), inserter, interactivity, layout (allowSizingOnChildren, default, ~~allowInheriting~~, ~~allowSwitching~~, ~~allowVerticalAlignment~~), spacing (blockGap, units), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** __unstableLocation, backgroundColor, customBackgroundColor, customOverlayBackgroundColor, customOverlayTextColor, customTextColor, hasIcon, icon, maxNestingLevel, openSubmenusOnClick, overlayBackgroundColor, overlayMenu, overlayTextColor, ref, rgbBackgroundColor, rgbTextColor, showSubmenuIcon, templateLock, textColor

## Custom Link
Expand Down
5 changes: 3 additions & 2 deletions lib/experimental/interactivity-api/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
/**
* Extend WordPress core blocks to use the Interactivity API.
*
* @package gutenberg
* @package Gutenberg
* @subpackage Interactivity API
*/

/**
Expand All @@ -17,7 +18,7 @@ function gutenberg_block_update_interactive_view_script( $metadata ) {
in_array( $metadata['name'], array( 'core/image' ), true ) &&
str_contains( $metadata['file'], 'build/block-library/blocks' )
) {
$metadata['viewScript'] = array( 'file:./interactivity.min.js' );
$metadata['viewScript'] = array( 'file:./view-interactivity.min.js' );
}
return $metadata;
}
Expand Down
77 changes: 77 additions & 0 deletions lib/experimental/interactivity-api/class-wp-directive-context.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php
/**
* Context data implementation.
*
* @package Gutenberg
* @subpackage Interactivity API
*/

/**
* This is a data structure to hold the current context.
*
* Whenever encountering a `data-wp-context` directive, we need to update
* the context with the data found in that directive. Conversely,
* when "leaving" that context (by encountering a closing tag), we
* need to reset the context to its previous state. This means that
* we actually need sort of a stack to keep track of all nested contexts.
*
* Example:
*
* <div data-wp-context='{ "foo": 123 }'>
* <!-- foo should be 123 here. -->
* <div data-wp-context='{ "foo": 456 }'>
* <!-- foo should be 456 here. -->
* </div>
* <!-- foo should be reset to 123 here. -->
* </div>
*/
class WP_Directive_Context {
/**
* The stack used to store contexts internally.
*
* @var array An array of contexts.
*/
protected $stack = array( array() );

/**
* Constructor.
*
* Accepts a context as an argument to initialize this with.
*
* @param array $context A context.
*/
function __construct( $context = array() ) {
$this->set_context( $context );
}

/**
* Return the current context.
*
* @return array The current context.
*/
public function get_context() {
return end( $this->stack );
}

/**
* Set the current context.
*
* @param array $context The context to be set.
*
* @return void
*/
public function set_context( $context ) {
if ( $context ) {
array_push( $this->stack, array_replace_recursive( $this->get_context(), $context ) );
}
}

/**
* Reset the context to its previous state.
*
* @return void
*/
public function rewind_context() {
array_pop( $this->stack );
}
}
193 changes: 193 additions & 0 deletions lib/experimental/interactivity-api/class-wp-directive-processor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
<?php
/**
* WP_Directive_Processor class
*
* @package Gutenberg
* @subpackage Interactivity API
*/

/**
* This processor is built on top of the HTML Tag Processor and augments its
* capabilities to process the Interactivity API directives.
*
* IMPORTANT DISCLAIMER: This code is highly experimental and its only purpose
* is to provide a way to test the server-side rendering of the Interactivity
* API. Most of this code will be discarded once the HTML Processor is
* available. Please restrain from investing unnecessary time and effort trying
* to improve this code.
*/
class WP_Directive_Processor extends WP_HTML_Tag_Processor {
/**
* Find the matching closing tag for an opening tag.
*
* When called while on an open tag, traverse the HTML until we find the
* matching closing tag, respecting any in-between content, including nested
* tags of the same name. Return false when called on a closing or void tag,
* or if no matching closing tag was found.
*
* @return bool Whether a matching closing tag was found.
*/
public function next_balanced_closer() {
$depth = 0;

$tag_name = $this->get_tag();

if ( self::is_html_void_element( $tag_name ) ) {
return false;
}

while ( $this->next_tag(
array(
'tag_name' => $tag_name,
'tag_closers' => 'visit',
)
) ) {
if ( ! $this->is_tag_closer() ) {
$depth++;
continue;
}

if ( 0 === $depth ) {
return true;
}

$depth--;
}

return false;
}

/**
* Return the content between two balanced tags.
*
* When called on an opening tag, return the HTML content found between that
* opening tag and its matching closing tag.
*
* @return string The content between the current opening and its matching
* closing tag.
*/
public function get_inner_html() {
$bookmarks = $this->get_balanced_tag_bookmarks();
if ( ! $bookmarks ) {
return false;
}
list( $start_name, $end_name ) = $bookmarks;

$start = $this->bookmarks[ $start_name ]->end + 1;
$end = $this->bookmarks[ $end_name ]->start;

$this->seek( $start_name ); // Return to original position.
$this->release_bookmark( $start_name );
$this->release_bookmark( $end_name );

return substr( $this->html, $start, $end - $start );
}

/**
* Set the content between two balanced tags.
*
* When called on an opening tag, set the HTML content found between that
* opening tag and its matching closing tag.
*
* @param string $new_html The string to replace the content between the
* matching tags with.
*
* @return bool Whether the content was successfully replaced.
*/
public function set_inner_html( $new_html ) {
$this->get_updated_html(); // Apply potential previous updates.

$bookmarks = $this->get_balanced_tag_bookmarks();
if ( ! $bookmarks ) {
return false;
}
list( $start_name, $end_name ) = $bookmarks;

$start = $this->bookmarks[ $start_name ]->end + 1;
$end = $this->bookmarks[ $end_name ]->start;

$this->seek( $start_name ); // Return to original position.
$this->release_bookmark( $start_name );
$this->release_bookmark( $end_name );

$this->lexical_updates[] = new WP_HTML_Text_Replacement( $start, $end, $new_html );
return true;
}

/**
* Return a pair of bookmarks for the current opening tag and the matching
* closing tag.
*
* @return array|false A pair of bookmarks, or false if there's no matching
* closing tag.
*/
public function get_balanced_tag_bookmarks() {
$i = 0;
while ( array_key_exists( 'start' . $i, $this->bookmarks ) ) {
++$i;
}
$start_name = 'start' . $i;

$this->set_bookmark( $start_name );
if ( ! $this->next_balanced_closer() ) {
$this->release_bookmark( $start_name );
return false;
}

$i = 0;
while ( array_key_exists( 'end' . $i, $this->bookmarks ) ) {
++$i;
}
$end_name = 'end' . $i;
$this->set_bookmark( $end_name );

return array( $start_name, $end_name );
}

/**
* Whether a given HTML element is void (e.g. <br>).
*
* @param string $tag_name The element in question.
* @return bool True if the element is void.
*
* @see https://html.spec.whatwg.org/#elements-2
*/
public static function is_html_void_element( $tag_name ) {
switch ( $tag_name ) {
case 'AREA':
case 'BASE':
case 'BR':
case 'COL':
case 'EMBED':
case 'HR':
case 'IMG':
case 'INPUT':
case 'LINK':
case 'META':
case 'SOURCE':
case 'TRACK':
case 'WBR':
return true;

default:
return false;
}
}

/**
* Extract and return the directive type and the the part after the double
* hyphen from an attribute name (if present), in an array format.
*
* Examples:
*
* 'wp-island' => array( 'wp-island', null )
* 'wp-bind--src' => array( 'wp-bind', 'src' )
* 'wp-thing--and--thang' => array( 'wp-thing', 'and--thang' )
*
* @param string $name The attribute name.
* @return array The resulting array
*/
public static function parse_attribute_name( $name ) {
return explode( '--', $name, 2 );
}
}
Loading

0 comments on commit 9b8d5c1

Please sign in to comment.