Skip to content
Open
46 changes: 32 additions & 14 deletions plugins/view-transitions/includes/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,22 @@
*/
function plvt_get_view_transition_animation_labels(): array {
return array(
'fade' => _x( 'Fade (default)', 'animation label', 'view-transitions' ),
'slide-from-right' => _x( 'Slide (from right)', 'animation label', 'view-transitions' ),
'slide-from-left' => _x( 'Slide (from left)', 'animation label', 'view-transitions' ),
'slide-from-bottom' => _x( 'Slide (from bottom)', 'animation label', 'view-transitions' ),
'slide-from-top' => _x( 'Slide (from top)', 'animation label', 'view-transitions' ),
'swipe-from-right' => _x( 'Swipe (from right)', 'animation label', 'view-transitions' ),
'swipe-from-left' => _x( 'Swipe (from left)', 'animation label', 'view-transitions' ),
'swipe-from-bottom' => _x( 'Swipe (from bottom)', 'animation label', 'view-transitions' ),
'swipe-from-top' => _x( 'Swipe (from top)', 'animation label', 'view-transitions' ),
'wipe-from-right' => _x( 'Wipe (from right)', 'animation label', 'view-transitions' ),
'wipe-from-left' => _x( 'Wipe (from left)', 'animation label', 'view-transitions' ),
'wipe-from-bottom' => _x( 'Wipe (from bottom)', 'animation label', 'view-transitions' ),
'wipe-from-top' => _x( 'Wipe (from top)', 'animation label', 'view-transitions' ),
'fade' => _x( 'Fade (default)', 'animation label', 'view-transitions' ),
'slide-from-right' => _x( 'Slide (from right)', 'animation label', 'view-transitions' ),
'slide-from-left' => _x( 'Slide (from left)', 'animation label', 'view-transitions' ),
'slide-from-bottom' => _x( 'Slide (from bottom)', 'animation label', 'view-transitions' ),
'slide-from-top' => _x( 'Slide (from top)', 'animation label', 'view-transitions' ),
'slide-chronological-pagination' => _x( 'Slide (Chronological and Pagination)', 'animation label', 'view-transitions' ),
'swipe-from-right' => _x( 'Swipe (from right)', 'animation label', 'view-transitions' ),
'swipe-from-left' => _x( 'Swipe (from left)', 'animation label', 'view-transitions' ),
'swipe-from-bottom' => _x( 'Swipe (from bottom)', 'animation label', 'view-transitions' ),
'swipe-from-top' => _x( 'Swipe (from top)', 'animation label', 'view-transitions' ),
'swipe-chronological-pagination' => _x( 'Swipe (Chronological and Pagination)', 'animation label', 'view-transitions' ),
'wipe-from-right' => _x( 'Wipe (from right)', 'animation label', 'view-transitions' ),
'wipe-from-left' => _x( 'Wipe (from left)', 'animation label', 'view-transitions' ),
'wipe-from-bottom' => _x( 'Wipe (from bottom)', 'animation label', 'view-transitions' ),
'wipe-from-top' => _x( 'Wipe (from top)', 'animation label', 'view-transitions' ),
'wipe-chronological-pagination' => _x( 'Wipe (Chronological and Pagination)', 'animation label', 'view-transitions' ),
);
}

Expand Down Expand Up @@ -231,7 +234,22 @@ function plvt_apply_settings_to_theme_support(): void {
// Apply the settings.
$args['default-animation'] = $options['default_transition_animation'];
$args['default-animation-duration'] = absint( $options['default_transition_animation_duration'] );
$selector_options = array(

// Automatically enable chronological and pagination animations for special animation options.
$chronological_pagination_animations = array(
'slide-chronological-pagination' => 'slide',
'swipe-chronological-pagination' => 'swipe',
'wipe-chronological-pagination' => 'wipe',
);
if ( isset( $chronological_pagination_animations[ $args['default-animation'] ] ) ) {
$base_animation = $chronological_pagination_animations[ $args['default-animation'] ];
$args['chronological-forwards-animation'] = $base_animation . '-from-right';
$args['chronological-backwards-animation'] = $base_animation . '-from-left';
$args['pagination-forwards-animation'] = $base_animation . '-from-right';
$args['pagination-backwards-animation'] = $base_animation . '-from-left';
}
Comment on lines +244 to +250
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

When the special *-chronological-pagination option is selected, $args['default-animation'] remains set to that value, but it is not a registered animation slug/alias in the animation registry. As a result, the default transition falls back to the browser default (fade) instead of the intended slide/swipe/wipe. Map $args['default-animation'] to a real registry alias/slug (e.g. the base animation or a concrete direction) when enabling these special options.

Copilot uses AI. Check for mistakes.

$selector_options = array(
'global' => array(
'header_selector' => 'header',
'main_selector' => 'main',
Expand Down
130 changes: 122 additions & 8 deletions plugins/view-transitions/includes/theme.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,22 @@ function plvt_sanitize_view_transitions_theme_support(): void {
$args = $_wp_theme_features['view-transitions'];

$defaults = array(
'post-selector' => '.wp-block-post.post, article.post, body.single main',
'global-transition-names' => array(
'post-selector' => '.wp-block-post.post, article.post, body.single main',
'global-transition-names' => array(
'header' => 'header',
'main' => 'main',
),
'post-transition-names' => array(
'post-transition-names' => array(
'.wp-block-post-title, .entry-title' => 'post-title',
'.wp-post-image' => 'post-thumbnail',
'.wp-block-post-content, .entry-content' => 'post-content',
),
'default-animation' => 'fade',
'default-animation-duration' => 400,
'default-animation' => 'fade',
'default-animation-duration' => 400,
'chronological-forwards-animation' => false,
'chronological-backwards-animation' => false,
'pagination-forwards-animation' => false,
'pagination-backwards-animation' => false,
);

// If no specific `$args` were provided, simply use the defaults.
Expand All @@ -102,8 +106,21 @@ function plvt_sanitize_view_transitions_theme_support(): void {
if ( ! is_array( $args['post-transition-names'] ) ) {
$args['post-transition-names'] = array();
}
}

// If specific transition animations match the default animations, they are irrelevant.
if ( $args['chronological-forwards-animation'] === $args['default-animation'] ) {
$args['chronological-forwards-animation'] = false;
}
if ( $args['chronological-backwards-animation'] === $args['default-animation'] ) {
$args['chronological-backwards-animation'] = false;
}
if ( $args['pagination-forwards-animation'] === $args['default-animation'] ) {
$args['pagination-forwards-animation'] = false;
}
if ( $args['pagination-backwards-animation'] === $args['default-animation'] ) {
$args['pagination-backwards-animation'] = false;
}
}
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$_wp_theme_features['view-transitions'] = $args;
}
Expand All @@ -124,7 +141,7 @@ function plvt_register_view_transition_animations( PLVT_View_Transition_Animatio
* animation.
*/
$is_specific_target_name = static function ( string $alias, array $args ): bool {
return '*' === $args['target-name'] ? false : true;
return ! ( '*' === $args['target-name'] );
};

/*
Expand Down Expand Up @@ -293,8 +310,12 @@ function plvt_register_view_transition_animations( PLVT_View_Transition_Animatio
* Loads view transitions based on the current configuration.
*
* @since 1.0.0
*
* @global WP_Rewrite $wp_rewrite
*/
function plvt_load_view_transitions(): void {
global $wp_rewrite;

if ( ! current_theme_supports( 'view-transitions' ) ) {
return;
}
Expand Down Expand Up @@ -324,7 +345,11 @@ function plvt_load_view_transitions(): void {
*/
if (
( ! is_array( $theme_support['global-transition-names'] ) || count( $theme_support['global-transition-names'] ) === 0 ) &&
( ! is_array( $theme_support['post-transition-names'] ) || count( $theme_support['post-transition-names'] ) === 0 )
( ! is_array( $theme_support['post-transition-names'] ) || count( $theme_support['post-transition-names'] ) === 0 ) &&
false === $theme_support['chronological-forwards-animation'] &&
false === $theme_support['chronological-backwards-animation'] &&
false === $theme_support['pagination-forwards-animation'] &&
false === $theme_support['pagination-backwards-animation']
) {
return;
}
Expand All @@ -336,11 +361,40 @@ function plvt_load_view_transitions(): void {
),
);

$additional_transition_types = array(
'chronological-forwards',
'chronological-backwards',
'pagination-forwards',
'pagination-backwards',
);

Comment on lines +364 to +370
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

New behavior is introduced for additional transition types (chronological/pagination) and CSS scoping, but the existing PHP unit tests in plugins/view-transitions/tests/test-theme.php don’t cover any of this (e.g. ensuring disabled types remain disabled, and enabled types enqueue scoped CSS). Please add/update tests around the additional transition type handling to prevent regressions.

Copilot uses AI. Check for mistakes.
foreach ( $additional_transition_types as $transition_type ) {
if ( isset( $theme_support[ $transition_type . '-animation' ] ) ) {
$additional_animation_args = isset( $theme_support[ $transition_type . '-animation-args' ] ) ? (array) $theme_support[ $transition_type . '-animation-args' ] : array();
$additional_animation_stylesheet = $animation_registry->get_animation_stylesheet( $theme_support[ $transition_type . '-animation' ], $additional_animation_args );
if ( '' !== $additional_animation_stylesheet ) {
wp_add_inline_style(
'plvt-view-transitions',
'@media (prefers-reduced-motion: no-preference) {' . plvt_scope_animation_stylesheet_to_transition_type( $additional_animation_stylesheet, $transition_type ) . '}'
);
Comment on lines +373 to +379
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The additional transition animation stylesheet is not passed through plvt_inject_animation_duration(), so chronological/pagination animations will ignore the configured default-animation-duration and instead use the CSS file’s default (typically 1s). Apply the same duration injection used for the default animation to each additional transition stylesheet before scoping/adding it.

Copilot uses AI. Check for mistakes.
}

$animations_js_config[ $transition_type ] = array(
'useGlobalTransitionNames' => $animation_registry->use_animation_global_transition_names( $theme_support[ $transition_type . '-animation' ], $additional_animation_args ),
'usePostTransitionNames' => $animation_registry->use_animation_post_transition_names( $theme_support[ $transition_type . '-animation' ], $additional_animation_args ),
'targetName' => $additional_animation_args['target-name'] ?? '*', // Special argument.
);
} else {
$animations_js_config[ $transition_type ] = false;
}
}
Comment on lines +371 to +390
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

In the additional transition types loop, isset( $theme_support[ $transition_type . '-animation' ] ) will be true even when the value is false (default), so disabled transition types get treated as enabled and produce a truthy JS config entry. This causes determineTransitionType() to select e.g. chronological-forwards even when the theme support explicitly disables it. Use a truthiness/type check (e.g., non-empty string) rather than isset() and only build $animations_js_config[$transition_type] when an actual animation alias is configured; otherwise set it to false.

Copilot uses AI. Check for mistakes.

$config = array(
'postSelector' => $theme_support['post-selector'],
'globalTransitionNames' => $theme_support['global-transition-names'],
'postTransitionNames' => $theme_support['post-transition-names'],
'animations' => $animations_js_config,
'paginationBase' => $wp_rewrite->pagination_base,
);

// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
Expand Down Expand Up @@ -389,3 +443,63 @@ function plvt_inject_animation_duration( string $css, int $animation_duration ):

return $css;
}

/**
* Scopes the given view transition animation CSS to apply only to a specific transition type.
*
* @since n.e.x.t
* @access private
*
* @param string $css Animation stylesheet as inline CSS.
* @param string $transition_type Transition type to scope the CSS to.
* @return string Scoped animation stylesheet.
*/
function plvt_scope_animation_stylesheet_to_transition_type( string $css, string $transition_type ): string {
$indent = static function ( string $input, $indent_tabs = 1 ): string {
return implode(
"\n",
array_map(
static function ( string $line ) use ( $indent_tabs ): string {
return str_repeat( "\t", $indent_tabs ) . $line;
},
explode( "\n", $input )
)
);
};

// This is very fragile, but it works well enough for now. TODO: Find a better solution to scope the CSS selectors.
if ( (bool) preg_match_all( '/(\s*)([^{}]+)\{[^{}]*?\}/m', $css, $matches ) ) {
// Wrap all `::view-transition-*` selectors to scope them to the transition type.
$view_transition_rule_pattern = '/::view-transition-/';

foreach ( $matches[0] as $index => $match ) {
$rule = $match;
$rule_name = $matches[2][ $index ];
if ( (bool) preg_match( $view_transition_rule_pattern, $rule_name ) ) {
$rule_whitespace = $matches[1][ $index ];
$prefixed_rule_name = preg_replace( $view_transition_rule_pattern, '&\0', $rule_name );
if ( null === $prefixed_rule_name ) {
continue;
}

$rule = str_replace( $rule_name, $prefixed_rule_name, $rule );

if ( str_contains( $rule, "\n" ) ) { // Non-minified.
$rule = $rule_whitespace .
"html:active-view-transition-type($transition_type) {\n" .
$indent( substr( $rule, strlen( $rule_whitespace ) ), 1 ) .
"\n}";
} else { // Minified.
$rule = $rule_whitespace .
"html:active-view-transition-type($transition_type){" .
substr( $rule, strlen( $rule_whitespace ) ) .
'}';
}

// Replace the original rule with the wrapped/scoped one.
$css = str_replace( $match, $rule, $css );
}
}
}
return $css;
}
5 changes: 5 additions & 0 deletions plugins/view-transitions/js/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ export type ViewTransitionsConfig = {
globalTransitionNames?: Record< string, string >;
postTransitionNames?: Record< string, string >;
animations?: Record< string, ViewTransitionAnimationConfig >;
paginationBase: string;
};

export type InitViewTransitionsFunction = (
config: ViewTransitionsConfig
) => void;

export type NavigationHistoryEntry = {
url: string;
};
Comment on lines +18 to +20
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

While adding NavigationHistoryEntry, note that the runtime config.animations now includes values that aren’t represented in the TS types: plvt_load_view_transitions() can emit false for disabled transition types and currently also emits a targetName field. Update ViewTransitionsConfig.animations / ViewTransitionAnimationConfig accordingly (or remove unused fields from the JS config) so the typings match actual data.

Copilot uses AI. Check for mistakes.

declare global {
interface Window {
plvtInitViewTransitions?: InitViewTransitionsFunction;
Expand Down
Loading
Loading