From d4f504b09e2b93a93a51a255992896e147d4602c Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Tue, 17 Mar 2026 12:52:56 +0530 Subject: [PATCH 01/40] fix: save dynamic post content value --- inc/plugins/class-dynamic-content.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inc/plugins/class-dynamic-content.php b/inc/plugins/class-dynamic-content.php index 0f66b8ef5..ca6861400 100644 --- a/inc/plugins/class-dynamic-content.php +++ b/inc/plugins/class-dynamic-content.php @@ -727,7 +727,8 @@ class_exists( '\Neve_Pro\Modules\Custom_Layouts\Module' ) if ( 'postContent' === $data['type'] ) { // To do not trigger postContent action if the given content contains the postContent dynamic tag, because it will cause an infinite loop. - $content = get_the_content( $data['context'] ); + $post = get_post( $data['context'] ); + $content = $post->post_content; if ( isset( $post ) && strpos( $content, 'data-type="postContent"' ) ) { $key = $this->get_exception_key( $data, $post->ID ); if ( $key ) { From f0e95c5ffc3aa65ac9d2a60a7345cecd34e9891c Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Tue, 17 Mar 2026 14:19:46 +0530 Subject: [PATCH 02/40] refactor: update slow animation speed --- src/animation/frontend/count/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/animation/frontend/count/index.js b/src/animation/frontend/count/index.js index e4d1d3ff4..c729f0138 100644 --- a/src/animation/frontend/count/index.js +++ b/src/animation/frontend/count/index.js @@ -11,7 +11,7 @@ const MAX_PARENT_SEARCH = 3; const speedConfig = { 'none': undefined, 'o-count-slower': 3, - 'o-count-slow': 2, + 'o-count-slow': 2.5, 'o-count-fast': 1.5, 'o-count-faster': 1 }; From 723894b3ee37580a3f13d062933c5b78ad4cb078 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 18 Mar 2026 18:11:20 +0530 Subject: [PATCH 03/40] fix: check for WP_Post to prevent errors --- inc/plugins/class-dynamic-content.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/inc/plugins/class-dynamic-content.php b/inc/plugins/class-dynamic-content.php index ca6861400..e972800cb 100644 --- a/inc/plugins/class-dynamic-content.php +++ b/inc/plugins/class-dynamic-content.php @@ -727,9 +727,13 @@ class_exists( '\Neve_Pro\Modules\Custom_Layouts\Module' ) if ( 'postContent' === $data['type'] ) { // To do not trigger postContent action if the given content contains the postContent dynamic tag, because it will cause an infinite loop. - $post = get_post( $data['context'] ); + $post = get_post( $data['context'] ); + if ( ! $post instanceof \WP_Post ) { + return $data; + } + $content = $post->post_content; - if ( isset( $post ) && strpos( $content, 'data-type="postContent"' ) ) { + if ( strpos( $content, 'data-type="postContent"' ) ) { $key = $this->get_exception_key( $data, $post->ID ); if ( $key ) { $data[ $key ] = true; From 69b7acf4ace99ed336014dc3aeea7f9ffb3842fa Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Thu, 19 Mar 2026 17:18:47 +0530 Subject: [PATCH 04/40] fix: prevent excerpt infinite loop --- inc/plugins/class-dynamic-content.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inc/plugins/class-dynamic-content.php b/inc/plugins/class-dynamic-content.php index e972800cb..a73d15165 100644 --- a/inc/plugins/class-dynamic-content.php +++ b/inc/plugins/class-dynamic-content.php @@ -438,7 +438,9 @@ public function get_excerpt( $data ) { $key = $this->get_exception_key( $data, $post->ID ); if ( ! isset( $data[ $key ] ) ) { + remove_filter( 'render_block', array( $this, 'apply_dynamic_content' ), 10 ); $excerpt = wp_trim_excerpt( '', $post ); + add_filter( 'render_block', array( $this, 'apply_dynamic_content' ), 10 ); } } From d368662a8b730c4ebfcbebacc51b3bbda7c35e85 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Fri, 20 Mar 2026 17:16:55 +0530 Subject: [PATCH 05/40] fix: update content retrieval --- inc/plugins/class-dynamic-content.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/inc/plugins/class-dynamic-content.php b/inc/plugins/class-dynamic-content.php index a73d15165..80354989d 100644 --- a/inc/plugins/class-dynamic-content.php +++ b/inc/plugins/class-dynamic-content.php @@ -733,8 +733,7 @@ class_exists( '\Neve_Pro\Modules\Custom_Layouts\Module' ) if ( ! $post instanceof \WP_Post ) { return $data; } - - $content = $post->post_content; + $content = get_the_content( $data['context'] ); if ( strpos( $content, 'data-type="postContent"' ) ) { $key = $this->get_exception_key( $data, $post->ID ); if ( $key ) { From 79bfde23a3be1428034a077c92bd3766f446f705 Mon Sep 17 00:00:00 2001 From: abaicus Date: Wed, 25 Mar 2026 19:14:22 +0200 Subject: [PATCH 06/40] fix: sanitize taxonomy filter input in atomic wind blocks --- inc/plugins/class-atomic-wind-blocks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/plugins/class-atomic-wind-blocks.php b/inc/plugins/class-atomic-wind-blocks.php index d49d4b28e..6de7f0c1c 100644 --- a/inc/plugins/class-atomic-wind-blocks.php +++ b/inc/plugins/class-atomic-wind-blocks.php @@ -484,7 +484,7 @@ public function render_query_loop( $block_content, $block ) { 'no_found_rows' => true, ); - $taxonomy_filter = isset( $block['attrs']['queryTaxonomy'] ) ? sanitize_key( $block['attrs']['queryTaxonomy'] ) : ''; + $taxonomy_filter = isset( $block['attrs']['queryTaxonomy'] ) ? sanitize_text_field( $block['attrs']['queryTaxonomy'] ) : ''; if ( $taxonomy_filter && false !== strpos( $taxonomy_filter, ':' ) ) { $parts = explode( ':', $taxonomy_filter, 2 ); $tax = sanitize_key( $parts[0] ); From 3e72b1b34b650007c32253ae700cd722550f4641 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Mon, 30 Mar 2026 12:45:22 +0530 Subject: [PATCH 07/40] fix: missing authorization in purchase verification --- inc/plugins/class-stripe-api.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/inc/plugins/class-stripe-api.php b/inc/plugins/class-stripe-api.php index 839de2754..f68ea6b13 100644 --- a/inc/plugins/class-stripe-api.php +++ b/inc/plugins/class-stripe-api.php @@ -230,10 +230,23 @@ public function save_customer_data( $session_id ) { $object['subscription_id'] = $session['subscription']; } + $items = $this->create_request( 'session_items', $session_id ); + $paid_product_ids = array(); + if ( ! empty( $items['data'] ) ) { + foreach ( $items['data'] as $item ) { + $price = $this->create_request( 'price', $item['price']['id'] ); + if ( isset( $price['product'] ) ) { + $paid_product_ids[] = $price['product']; + } + } + } + $queries = []; parse_str( $session['success_url'], $queries ); - if ( isset( $queries['product_id'] ) ) { + if ( isset( $queries['product_id'] ) && in_array( $queries['product_id'], $paid_product_ids, true ) ) { $object['product_id'] = $queries['product_id']; + } else { + return; } array_push( $data, $object ); From 4395af3db4f0a4279563761118384b55793b3803 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Mon, 30 Mar 2026 14:20:10 +0530 Subject: [PATCH 08/40] fix: improve product id retrieval --- inc/plugins/class-stripe-api.php | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/inc/plugins/class-stripe-api.php b/inc/plugins/class-stripe-api.php index f68ea6b13..c8a8365f4 100644 --- a/inc/plugins/class-stripe-api.php +++ b/inc/plugins/class-stripe-api.php @@ -232,17 +232,38 @@ public function save_customer_data( $session_id ) { $items = $this->create_request( 'session_items', $session_id ); $paid_product_ids = array(); - if ( ! empty( $items['data'] ) ) { - foreach ( $items['data'] as $item ) { + if ( is_wp_error( $items ) || empty( $items['data'] ) || ! is_array( $items['data'] ) ) { + return; + } + + foreach ( $items['data'] as $item ) { + $product_id = null; + + if ( isset( $item['price']['product'] ) && ! empty( $item['price']['product'] ) ) { + $product_id = $item['price']['product']; + } elseif ( isset( $item['price']['id'] ) && ! empty( $item['price']['id'] ) ) { $price = $this->create_request( 'price', $item['price']['id'] ); + + if ( is_wp_error( $price ) || ! isset( $price['product'] ) ) { + continue; + } + if ( isset( $price['product'] ) ) { - $paid_product_ids[] = $price['product']; + $product_id = $price['product']; } } + + if ( null !== $product_id ) { + $paid_product_ids[] = $product_id; + } + } + + $queries = []; + $query_string = wp_parse_url( $session['success_url'], PHP_URL_QUERY ); + if ( ! empty( $query_string ) ) { + parse_str( $query_string, $queries ); } - $queries = []; - parse_str( $session['success_url'], $queries ); if ( isset( $queries['product_id'] ) && in_array( $queries['product_id'], $paid_product_ids, true ) ) { $object['product_id'] = $queries['product_id']; } else { From 702709426cf7657558df26cd9524cc60c110ad05 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Mon, 30 Mar 2026 19:23:10 +0530 Subject: [PATCH 09/40] fix: add test for incorrect price ID purchase --- tests/stripe-http-client-mock.php | 119 +++++++++++++++++------------- tests/test-stripe-api-class.php | 13 ++++ 2 files changed, 81 insertions(+), 51 deletions(-) diff --git a/tests/stripe-http-client-mock.php b/tests/stripe-http-client-mock.php index 7604c3f30..9add76065 100644 --- a/tests/stripe-http-client-mock.php +++ b/tests/stripe-http-client-mock.php @@ -155,63 +155,80 @@ private function mockCreateSession() ); } - private function mockGetSession() + private function mockGetSession( $path = '' ) { - return json_encode( - [ - 'id' => 'sess_1', - 'status' => 'complete', - 'customer' => 'cus_1', - 'customer_details' => [ - 'email' => 'test@test.com' - ], - 'mode' => 'payment', - 'payment_status' => 'paid', - 'payment_intent' => 'pi_1', - 'object' => 'checkout.session', - 'success_url' => 'https://example.com/success?product_id=prod_1', - 'complete' => true - ] + preg_match( '#^/v1/checkout/sessions/(\w+)$#', $path, $matches ); + $session_id = isset( $matches[1] ) ? $matches[1] : 'sess_1'; + + $base = array( + 'status' => 'complete', + 'customer' => 'cus_1', + 'customer_details' => array( 'email' => 'test@test.com' ), + 'mode' => 'payment', + 'payment_status' => 'paid', + 'payment_intent' => 'pi_1', + 'object' => 'checkout.session', ); + + switch ( $session_id ) { + case 'sess_wrong_price_product': + return json_encode( array_merge( $base, array( + 'id' => 'sess_wrong_price_product', + 'success_url' => 'https://example.com/success?product_id=prod_1', + ) ) ); + + default: + return json_encode( array_merge( $base, array( + 'id' => 'sess_1', + 'success_url' => 'https://example.com/success?product_id=prod_1', + 'complete' => true, + ) ) ); + } } - private function mockSessionItems() + private function mockSessionItems( $path = '' ) { - return json_encode( - [ - 'object' => 'list', - 'data' => [ - [ - 'id' => 'item_1', - 'quantity' => 1, - 'object' => 'item', - 'price' => [ - 'id' => 'price_1', - 'product' => 'prod_1', - 'unit_amount' => 1000, - 'currency' => 'USD', - 'object' => 'price', - 'active' => true, - 'billing_scheme' => 'per_unit', - ] - ], - [ - 'id' => 'item_2', - 'quantity' => 2, - 'object' => 'item', - 'price' => [ - 'id' => 'price_1', - 'product' => 'prod_1', - 'unit_amount' => 1000, - 'currency' => 'USD', - 'object' => 'price', - 'active' => true, - 'billing_scheme' => 'per_unit', - ] - ] - ] - ] + preg_match( '#^/v1/checkout/sessions/(\w+)/line_items$#', $path, $matches ); + $session_id = isset( $matches[1] ) ? $matches[1] : 'sess_1'; + + $premium_item = array( + 'id' => 'item_1', + 'quantity' => 1, + 'object' => 'item', + 'price' => array( + 'id' => 'price_1', + 'product' => 'prod_1', + 'unit_amount' => 10000, + 'currency' => 'USD', + 'object' => 'price', + 'active' => true, + 'billing_scheme' => 'per_unit', + ), ); + + switch ( $session_id ) { + case 'sess_wrong_price_product': + // User actually paid with price_2 — NOT price_1 as the URL claims. + $item = $premium_item; + $item['price'] = array( + 'id' => 'price_2', + 'product' => 'prod_2', + 'unit_amount' => 100, + 'currency' => 'USD', + 'object' => 'price', + 'active' => true, + 'billing_scheme' => 'per_unit', + ); + break; + default: + $item = $premium_item; + break; + } + + return json_encode( array( + 'object' => 'list', + 'data' => array( $item ), + ) ); } private function mockGetSubscription() diff --git a/tests/test-stripe-api-class.php b/tests/test-stripe-api-class.php index be8e62ee2..036700c61 100644 --- a/tests/test-stripe-api-class.php +++ b/tests/test-stripe-api-class.php @@ -109,4 +109,17 @@ public function test_status_for_price_id() { $this->assertTrue( 'success' === $status ); } + + /** + * Test user purchase the product with correct price id. + */ + public function test_user_purchase_with_correct_price_id() { + wp_set_current_user( 1 ); + + $this->stripe_api->save_customer_data( 'sess_wrong_price_product' ); + $data = $this->stripe_api->get_customer_data(); + + $this->assertEmpty( $data ); + $this->assertFalse( $this->stripe_api->check_purchase( 'prod_1' ) ); + } } From af81f798c344cd6ad78a9b00eb0690ddc25e57a3 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Mon, 30 Mar 2026 17:45:29 +0300 Subject: [PATCH 10/40] chore: upgrade SDK to v3.3.51 --- composer.json | 2 +- composer.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index a151477f9..a75cee0dc 100644 --- a/composer.json +++ b/composer.json @@ -72,7 +72,7 @@ }, "minimum-stability": "dev", "require": { - "codeinwp/themeisle-sdk": "^3.2", + "codeinwp/themeisle-sdk": "^3.3", "masterminds/html5": "^2.7", "tubalmartin/cssmin": "^4.1", "wptt/webfont-loader": "^1.1", diff --git a/composer.lock b/composer.lock index f48d351f2..52487fe94 100644 --- a/composer.lock +++ b/composer.lock @@ -4,25 +4,25 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4530d8e3909c9fd175e971fb8ef4b055", + "content-hash": "4fddabbc0ea7df1b3c8c1cfa0e3698b7", "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.50", + "version": "3.3.51", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "3c1f8dfc2390e667bbc086c5d660900a7985efa6" + "reference": "bb2a8414b0418b18c68c9ff1df3d7fb10467928d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/3c1f8dfc2390e667bbc086c5d660900a7985efa6", - "reference": "3c1f8dfc2390e667bbc086c5d660900a7985efa6", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/bb2a8414b0418b18c68c9ff1df3d7fb10467928d", + "reference": "bb2a8414b0418b18c68c9ff1df3d7fb10467928d", "shasum": "" }, "require-dev": { "codeinwp/phpcs-ruleset": "dev-main", - "yoast/phpunit-polyfills": "^2.0" + "yoast/phpunit-polyfills": "^4.0" }, "type": "library", "notification-url": "https://packagist.org/downloads/", @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.50" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.51" }, - "time": "2025-11-25T19:36:35+00:00" + "time": "2026-03-30T07:58:49+00:00" }, { "name": "enshrined/svg-sanitize", @@ -3237,5 +3237,5 @@ "platform-overrides": { "php": "7.4" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } From 86c2552f535b3b3154d0bdb382fb3376b6946668 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 1 Apr 2026 12:21:04 +0530 Subject: [PATCH 11/40] fix: php undefined array key warning --- inc/class-base-css.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/class-base-css.php b/inc/class-base-css.php index a14e1758d..641366e24 100644 --- a/inc/class-base-css.php +++ b/inc/class-base-css.php @@ -150,7 +150,7 @@ public function get_google_fonts( $attr ) { 'fontfamily' => $attr['fontFamily'], 'fontvariant' => ( isset( $attr['fontVariant'] ) && ! empty( $attr['fontVariant'] ) ? array( $attr['fontVariant'] ) : array() ), ); - } elseif ( ! in_array( $attr['fontVariant'], self::$google_fonts[ $attr['fontFamily'] ]['fontvariant'], true ) ) { + } elseif ( isset( $attr['fontVariant'] ) && ! empty( $attr['fontVariant'] ) && ! in_array( $attr['fontVariant'], self::$google_fonts[ $attr['fontFamily'] ]['fontvariant'], true ) ) { array_push( self::$google_fonts[ $attr['fontFamily'] ]['fontvariant'], ( isset( $attr['fontStyle'] ) && 'italic' === $attr['fontStyle'] ) ? $attr['fontVariant'] . ':i' : $attr['fontVariant'] ); } } From 779830a839c9a6b22f3512d7ef1f0a84d6a9a83e Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 1 Apr 2026 12:29:07 +0530 Subject: [PATCH 12/40] fix: handle font variant --- inc/class-base-css.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/inc/class-base-css.php b/inc/class-base-css.php index 641366e24..b9c046b8d 100644 --- a/inc/class-base-css.php +++ b/inc/class-base-css.php @@ -150,8 +150,11 @@ public function get_google_fonts( $attr ) { 'fontfamily' => $attr['fontFamily'], 'fontvariant' => ( isset( $attr['fontVariant'] ) && ! empty( $attr['fontVariant'] ) ? array( $attr['fontVariant'] ) : array() ), ); - } elseif ( isset( $attr['fontVariant'] ) && ! empty( $attr['fontVariant'] ) && ! in_array( $attr['fontVariant'], self::$google_fonts[ $attr['fontFamily'] ]['fontvariant'], true ) ) { - array_push( self::$google_fonts[ $attr['fontFamily'] ]['fontvariant'], ( isset( $attr['fontStyle'] ) && 'italic' === $attr['fontStyle'] ) ? $attr['fontVariant'] . ':i' : $attr['fontVariant'] ); + } elseif ( isset( $attr['fontVariant'] ) && ! empty( $attr['fontVariant'] ) ) { + $font_variant = $attr['fontVariant']; + if ( ! in_array( $font_variant, self::$google_fonts[ $attr['fontFamily'] ]['fontvariant'], true ) ) { + self::$google_fonts[ $attr['fontFamily'] ]['fontvariant'][] = $font_variant; + } } } } From 4746cdb2bc1837a9e417f06f174a2bad02f05537 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 12:24:50 +0000 Subject: [PATCH 13/40] Initial plan From b587706bb09d30fd926c361b65b469fbf9367bd8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 12:31:01 +0000 Subject: [PATCH 14/40] Add color slug resolution for advanced-heading block Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- inc/class-base-css.php | 53 +++++++++++++++++++ inc/css/blocks/class-advanced-heading-css.php | 18 +++++++ src/blocks/blocks/advanced-heading/edit.js | 20 ++++--- src/blocks/helpers/helper-functions.js | 36 +++++++++++++ 4 files changed, 119 insertions(+), 8 deletions(-) diff --git a/inc/class-base-css.php b/inc/class-base-css.php index b9c046b8d..9a2077826 100644 --- a/inc/class-base-css.php +++ b/inc/class-base-css.php @@ -202,6 +202,59 @@ public static function hex2rgba( $color, $opacity = false ) { return $output; } + /** + * Resolve a color value which may be a slug from the theme color palette. + * If the value is a slug (doesn't start with # or rgb/hsl/var), attempts to resolve it from the palette. + * Otherwise, returns the value as-is. + * + * @param string|null $value The color value or slug. + * @return string|null The resolved color value. + * @since 3.1.5 + * @access public + */ + public static function resolve_color_value( $value ) { + if ( empty( $value ) ) { + return $value; + } + + // If it's already a color value (hex, rgb, hsl, var), return as-is. + if ( + strpos( $value, '#' ) === 0 || + strpos( $value, 'rgb' ) === 0 || + strpos( $value, 'hsl' ) === 0 || + strpos( $value, 'var(' ) === 0 + ) { + return $value; + } + + // Try to get the color palette from theme.json. + if ( function_exists( 'wp_get_global_settings' ) ) { + $global_settings = wp_get_global_settings(); + + // Get colors from different sources (theme, default, custom). + $palettes = array(); + if ( isset( $global_settings['color']['palette']['theme'] ) ) { + $palettes = array_merge( $palettes, $global_settings['color']['palette']['theme'] ); + } + if ( isset( $global_settings['color']['palette']['default'] ) ) { + $palettes = array_merge( $palettes, $global_settings['color']['palette']['default'] ); + } + if ( isset( $global_settings['color']['palette']['custom'] ) ) { + $palettes = array_merge( $palettes, $global_settings['color']['palette']['custom'] ); + } + + // Try to find the color by slug. + foreach ( $palettes as $color_obj ) { + if ( isset( $color_obj['slug'] ) && $color_obj['slug'] === $value ) { + return $color_obj['color']; + } + } + } + + // If we couldn't resolve it, return the original value. + return $value; + } + /** * Get Blocks CSS * diff --git a/inc/css/blocks/class-advanced-heading-css.php b/inc/css/blocks/class-advanced-heading-css.php index 452c9ed4f..f7d25452f 100644 --- a/inc/css/blocks/class-advanced-heading-css.php +++ b/inc/css/blocks/class-advanced-heading-css.php @@ -44,10 +44,16 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'headingColor', + 'format' => function ( $value, $attrs ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'background', 'value' => 'backgroundColor', + 'format' => function ( $value, $attrs ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'font-family', @@ -225,10 +231,16 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'highlightColor', + 'format' => function ( $value, $attrs ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'background', 'value' => 'highlightBackground', + 'format' => function ( $value, $attrs ) { + return Base_CSS::resolve_color_value( $value ); + }, ), ); @@ -537,6 +549,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'linkColor', + 'format' => function ( $value, $attrs ) { + return Base_CSS::resolve_color_value( $value ); + }, ), ), ) @@ -549,6 +564,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'linkHoverColor', + 'format' => function ( $value, $attrs ) { + return Base_CSS::resolve_color_value( $value ); + }, ), ), ) diff --git a/src/blocks/blocks/advanced-heading/edit.js b/src/blocks/blocks/advanced-heading/edit.js index 2cfab5e84..aab9aa191 100644 --- a/src/blocks/blocks/advanced-heading/edit.js +++ b/src/blocks/blocks/advanced-heading/edit.js @@ -17,7 +17,8 @@ import { import { RichText, - useBlockProps + useBlockProps, + useSetting } from '@wordpress/block-editor'; import { @@ -33,7 +34,7 @@ import { blockInit } from '../../helpers/block-utility.js'; import Controls from './controls.js'; import Inspector from './inspector.js'; import googleFontsLoader from '../../helpers/google-fonts.js'; -import { boxValues, _cssBlock, _px } from '../../helpers/helper-functions'; +import { boxValues, _cssBlock, _px, resolveColorValue } from '../../helpers/helper-functions'; import { makeBox } from '../../plugins/copy-paste/utils'; import { useDarkBackground, @@ -63,6 +64,9 @@ const Edit = ({ useDarkBackground( attributes.backgroundColor, attributes, setAttributes ); + // Get the color palette from theme.json or block settings + const colorPalette = useSetting( 'color.palette' ) || []; + const changeContent = value => { setAttributes({ content: value }); }; @@ -153,14 +157,14 @@ const Edit = ({ attributes.fontSizeTablet, attributes.fontSizeMobile ]), - color: attributes.headingColor, + color: resolveColorValue( attributes.headingColor, colorPalette ), fontFamily: attributes.fontFamily || undefined, fontWeight: 'regular' === attributes.fontVariant ? 'normal' : attributes.fontVariant, fontStyle: attributes.fontStyle || undefined, textTransform: attributes.textTransform || undefined, lineHeight: ( ( ! isString( attributes.lineHeight ) && 3 < attributes.lineHeight ) ? attributes.lineHeight + 'px' : attributes.lineHeight ) || undefined, letterSpacing: _px( attributes.letterSpacing ), - background: attributes.backgroundColor, + background: resolveColorValue( attributes.backgroundColor, colorPalette ), ...textShadowStyle, ...inlineStyle }, x => x?.includes?.( 'undefined' ) ); @@ -182,18 +186,18 @@ const Edit = ({ diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js index 507019a40..e5b7c8d35 100644 --- a/src/blocks/helpers/helper-functions.js +++ b/src/blocks/helpers/helper-functions.js @@ -360,6 +360,42 @@ export const lightnessFromColor = color => { return 128 > brightness ? 'dark' : 'light'; }; +/** + * Resolve a color value which may be a slug from the color palette. + * If the value is a slug (doesn't start with # or rgb/hsl/var), attempts to resolve it from the palette. + * Otherwise, returns the value as-is. + * + * @param {string|undefined} value The color value or slug + * @param {Array} palette Optional color palette array from useSetting('color.palette') + * @return {string|undefined} The resolved color value + */ +export const resolveColorValue = ( value, palette = null ) => { + if ( ! value ) { + return value; + } + + // If it's already a color value (hex, rgb, hsl, var), return as-is + if ( + value.startsWith( '#' ) || + value.startsWith( 'rgb' ) || + value.startsWith( 'hsl' ) || + value.startsWith( 'var(' ) + ) { + return value; + } + + // If no palette provided, we can't resolve, so return the value + if ( ! palette || ! Array.isArray( palette ) ) { + return value; + } + + // Try to find the color in the palette by slug + const colorObject = palette.find( color => color.slug === value ); + + // Return the color value if found, otherwise return original value + return colorObject?.color || value; +}; + /** * Return the values from a box type. * From d32169a50dd97e9b80cad5de69c9dc9717cd84d0 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 1 Apr 2026 16:19:09 +0530 Subject: [PATCH 15/40] Build project successfully with color slug resolution Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- package-lock.json | 91 ++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ec5060b1..0a52ec34b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -182,6 +182,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2043,6 +2044,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2066,6 +2068,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2191,6 +2194,7 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "dev": true, + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -3721,6 +3725,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz", "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==", "dev": true, + "peer": true, "dependencies": { "@octokit/auth-token": "^3.0.0", "@octokit/graphql": "^5.0.0", @@ -4196,6 +4201,7 @@ "integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.55.1" }, @@ -5461,6 +5467,7 @@ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -5826,8 +5833,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -5925,6 +5931,7 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -6164,6 +6171,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", "dev": true, + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -6216,6 +6224,7 @@ "version": "18.3.22", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.22.tgz", "integrity": "sha512-vUhG0YmQZ7kL/tmKLrD3g5zXbXXreZXB3pmROW8bg3CnLnpjkRVwUlLne7Ufa2r9yJ8+/6B73RzhAek5TBKh2Q==", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -6225,6 +6234,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -6351,6 +6361,7 @@ "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.40.tgz", "integrity": "sha512-u6kMFSBM9HcoTpUXnL6mt2HSzftqb3JgYV6oxIgL2dl6sX6aCa5k6SOkzv5DuZjBTPUE/dJltKtwwuqrkZHpfw==", "dev": true, + "peer": true, "dependencies": { "@types/node": "*", "@types/tapable": "^1", @@ -6837,6 +6848,7 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", "dev": true, + "peer": true, "dependencies": { "@babel/runtime": "^7.9.2" } @@ -7287,6 +7299,7 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", "dev": true, + "peer": true, "dependencies": { "@babel/runtime": "^7.9.2" } @@ -7369,6 +7382,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", @@ -8847,6 +8861,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.21.0", @@ -8882,6 +8897,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -9999,6 +10015,7 @@ "integrity": "sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.25.7", @@ -10115,6 +10132,7 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -10370,7 +10388,8 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/@wordpress/scripts/node_modules/eslint-plugin-jest": { "version": "27.9.0", @@ -10639,6 +10658,7 @@ "integrity": "sha512-X4UlrxDTH8oom9qXlcjnydsjAOD2BmB6yFmvS4Z2zdTzqqpRWb+fbqrH412+l+OUXmbzJlSXjlMFYPgYG12IAA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10991,6 +11011,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -11068,6 +11089,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12289,6 +12311,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", @@ -12767,8 +12790,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/chrome-launcher": { "version": "1.2.0", @@ -14102,7 +14124,6 @@ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", "dev": true, - "peer": true, "dependencies": { "node-fetch": "2.6.7" } @@ -14112,7 +14133,6 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dev": true, - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -14132,22 +14152,19 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/cross-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/cross-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, - "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -15018,7 +15035,8 @@ "version": "0.0.1445099", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1445099.tgz", "integrity": "sha512-GEuIbCLU2Iu6Sg05GeWS7ksijhOUZIDJD2YBUNRanK7SLKjeci1uxUUomu2VNvygQRuoq/vtnTYrgPZBEiYNMA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/diff": { "version": "4.0.2", @@ -15102,8 +15120,7 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/dom-serializer": { "version": "2.0.0", @@ -15660,6 +15677,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -20286,6 +20304,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -22009,7 +22028,6 @@ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -22247,6 +22265,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -22958,8 +22977,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/modify-values": { "version": "1.0.1", @@ -23495,6 +23513,7 @@ "resolved": "https://registry.npmjs.org/npm-package-json-lint/-/npm-package-json-lint-6.4.0.tgz", "integrity": "sha512-cuXAJJB1Rdqz0UO6w524matlBqDBjcNt7Ru+RDIu4y6RI1gVqiWBnylrK8sPRk81gGBA0X8hJbDXolVOoTc+sA==", "dev": true, + "peer": true, "dependencies": { "ajv": "^6.12.6", "ajv-errors": "^1.0.1", @@ -27505,6 +27524,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -28155,6 +28175,7 @@ "integrity": "sha512-1nMfdFjucm5hKvq0IClqZwK4FJkGXhRrQstOQ3P4vp8HxKrJEMFcY6RdBRVTdfQS/UlnX6gfbPuTvaqx/bDoeQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -28202,7 +28223,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -28217,7 +28237,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -28229,8 +28248,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/process-nextick-args": { "version": "2.0.1", @@ -28426,6 +28444,7 @@ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.9.0.tgz", "integrity": "sha512-HFdCeH/wx6QPz8EncafbCqJBqaCG1ENW75xg3cLFMRUoqZDgByT6HSueiumetT2uClZxwqj0qS4qMVZwLHRHHw==", "dev": true, + "peer": true, "dependencies": { "@puppeteer/browsers": "2.10.5", "chromium-bidi": "5.1.0", @@ -28485,7 +28504,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "peer": true, "dependencies": { "ms": "2.1.2" }, @@ -28502,15 +28520,13 @@ "version": "0.0.1045489", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1045489.tgz", "integrity": "sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/puppeteer/node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, - "peer": true, "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -28531,7 +28547,6 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, - "peer": true, "dependencies": { "pump": "^3.0.0" }, @@ -28546,15 +28561,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/puppeteer/node_modules/puppeteer-core": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.0.0.tgz", "integrity": "sha512-OljQ9W5M4cBX68vnOAGbcRkVENDHn6lfj6QYoGsnLQsxPAh6ExTQAhHauwdFdQkhYdDExZFWlKArnBONzeHY+g==", "dev": true, - "peer": true, "dependencies": { "cross-fetch": "3.1.5", "debug": "4.3.4", @@ -28576,7 +28589,6 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, - "peer": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -28589,7 +28601,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", "dev": true, - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -28761,6 +28772,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -28798,6 +28810,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -28846,6 +28859,7 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -29182,7 +29196,8 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -29854,6 +29869,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.0.tgz", "integrity": "sha512-ld+kQU8YTdGNjOLfRWBzewJpU5cwEv/h5yyqlSeJcj6Yh8U4TDA9UA5FPicqDz/xgRPWRSYIQNiFks21TbA9KQ==", "dev": true, + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -29955,6 +29971,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -30014,6 +30031,7 @@ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.5.tgz", "integrity": "sha512-NMPKdfpXTnPn49FDogMBi36SiBfXkSOJqCkk0E4iWOY1tusvvgBwqUmxTX1kmlT6kIYed9YwNKD1sfPpqa5yaA==", "dev": true, + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^9.0.2", "@semantic-release/error": "^3.0.0", @@ -31903,6 +31921,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", @@ -32304,6 +32323,7 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -33239,6 +33259,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -33359,6 +33380,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -34140,6 +34162,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", "dev": true, + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -34243,6 +34266,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -34341,6 +34365,7 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dev": true, + "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", From efb938a3a282e977570849ce5a9d3b821e7f4f0d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 12:38:04 +0000 Subject: [PATCH 16/40] Add useColorResolver hook for easier color slug resolution Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- src/blocks/blocks/advanced-heading/edit.js | 24 +++++++++++----------- src/blocks/helpers/utility-hooks.js | 16 ++++++++++++++- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/blocks/blocks/advanced-heading/edit.js b/src/blocks/blocks/advanced-heading/edit.js index aab9aa191..d4df589c2 100644 --- a/src/blocks/blocks/advanced-heading/edit.js +++ b/src/blocks/blocks/advanced-heading/edit.js @@ -17,8 +17,7 @@ import { import { RichText, - useBlockProps, - useSetting + useBlockProps } from '@wordpress/block-editor'; import { @@ -34,11 +33,12 @@ import { blockInit } from '../../helpers/block-utility.js'; import Controls from './controls.js'; import Inspector from './inspector.js'; import googleFontsLoader from '../../helpers/google-fonts.js'; -import { boxValues, _cssBlock, _px, resolveColorValue } from '../../helpers/helper-functions'; +import { boxValues, _cssBlock, _px } from '../../helpers/helper-functions'; import { makeBox } from '../../plugins/copy-paste/utils'; import { useDarkBackground, - useResponsiveAttributes + useResponsiveAttributes, + useColorResolver } from '../../helpers/utility-hooks.js'; const { attributes: defaultAttributes } = metadata; @@ -64,8 +64,8 @@ const Edit = ({ useDarkBackground( attributes.backgroundColor, attributes, setAttributes ); - // Get the color palette from theme.json or block settings - const colorPalette = useSetting( 'color.palette' ) || []; + // Get color resolver to handle theme color slugs + const resolveColor = useColorResolver(); const changeContent = value => { setAttributes({ content: value }); @@ -157,14 +157,14 @@ const Edit = ({ attributes.fontSizeTablet, attributes.fontSizeMobile ]), - color: resolveColorValue( attributes.headingColor, colorPalette ), + color: resolveColor( attributes.headingColor ), fontFamily: attributes.fontFamily || undefined, fontWeight: 'regular' === attributes.fontVariant ? 'normal' : attributes.fontVariant, fontStyle: attributes.fontStyle || undefined, textTransform: attributes.textTransform || undefined, lineHeight: ( ( ! isString( attributes.lineHeight ) && 3 < attributes.lineHeight ) ? attributes.lineHeight + 'px' : attributes.lineHeight ) || undefined, letterSpacing: _px( attributes.letterSpacing ), - background: resolveColorValue( attributes.backgroundColor, colorPalette ), + background: resolveColor( attributes.backgroundColor ), ...textShadowStyle, ...inlineStyle }, x => x?.includes?.( 'undefined' ) ); @@ -186,18 +186,18 @@ const Edit = ({ diff --git a/src/blocks/helpers/utility-hooks.js b/src/blocks/helpers/utility-hooks.js index 86805b688..6b6ff0a88 100644 --- a/src/blocks/helpers/utility-hooks.js +++ b/src/blocks/helpers/utility-hooks.js @@ -5,10 +5,12 @@ import { useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; +import { useSetting } from '@wordpress/block-editor'; + /** * Internal dependencies */ -import { buildResponsiveGetAttributes, buildResponsiveSetAttributes, lightnessFromColor } from './helper-functions.js'; +import { buildResponsiveGetAttributes, buildResponsiveSetAttributes, lightnessFromColor, resolveColorValue } from './helper-functions.js'; /** * Utiliy hook to get/set responsive attributes. @@ -67,3 +69,15 @@ export const useDarkBackground = ( backgroundColor, attributes, setAttributes, d setAttributes({ className: classes }); }, [ backgroundColor ]); }; + +/** + * Utility hook to resolve color slugs from the theme palette. + * Returns a function that can resolve a color value (which may be a slug) to its actual color. + * + * @return {Function} A function that resolves color values/slugs. + */ +export const useColorResolver = () => { + const colorPalette = useSetting( 'color.palette' ) || []; + + return ( colorValue ) => resolveColorValue( colorValue, colorPalette ); +}; From bf522a58a9ebcf5c3c136328073fd1a5828fd6f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 12:38:52 +0000 Subject: [PATCH 17/40] Add documentation for color slug resolution Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- docs/color-slug-resolution.md | 165 ++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 docs/color-slug-resolution.md diff --git a/docs/color-slug-resolution.md b/docs/color-slug-resolution.md new file mode 100644 index 000000000..5c6fe0c52 --- /dev/null +++ b/docs/color-slug-resolution.md @@ -0,0 +1,165 @@ +# Color Slug Resolution in Otter Blocks + +## Problem + +WordPress themes define color palettes in `theme.json` using color slugs (e.g., "primary", "base", "contrast"). Core WordPress blocks can reference these colors by slug, and they are automatically resolved to the actual color values. + +However, Otter Blocks were not resolving these color slugs, causing them to revert to defaults when a slug was used instead of a hex/rgb value. + +## Solution + +We've added color slug resolution utilities that work on both the JavaScript (editor) and PHP (frontend) sides. + +### JavaScript Solution + +#### Option 1: Use the `useColorResolver` Hook (Recommended) + +```javascript +import { useColorResolver } from '../../helpers/utility-hooks.js'; + +const Edit = ({ attributes, setAttributes }) => { + // Get the color resolver function + const resolveColor = useColorResolver(); + + // Use it to resolve any color attribute + const resolvedBackgroundColor = resolveColor(attributes.backgroundColor); + const resolvedTextColor = resolveColor(attributes.textColor); + + // Apply to styles + const style = { + backgroundColor: resolvedBackgroundColor, + color: resolvedTextColor + }; + + return
...
; +}; +``` + +#### Option 2: Use `resolveColorValue` Directly + +```javascript +import { useSetting } from '@wordpress/block-editor'; +import { resolveColorValue } from '../../helpers/helper-functions'; + +const Edit = ({ attributes, setAttributes }) => { + // Get the color palette + const colorPalette = useSetting('color.palette') || []; + + // Resolve colors + const resolvedColor = resolveColorValue(attributes.backgroundColor, colorPalette); + + return
...
; +}; +``` + +### PHP Solution + +In your block's CSS class (e.g., `class-my-block-css.php`), use the `Base_CSS::resolve_color_value()` method: + +```php +$css->add_item( + array( + 'properties' => array( + array( + 'property' => 'background-color', + 'value' => 'backgroundColor', + 'format' => function ( $value, $attrs ) { + return Base_CSS::resolve_color_value( $value ); + }, + ), + array( + 'property' => 'color', + 'value' => 'textColor', + 'format' => function ( $value, $attrs ) { + return Base_CSS::resolve_color_value( $value ); + }, + ), + ), + ) +); +``` + +## How It Works + +### Color Resolution Logic + +The resolver: +1. Checks if the value is already a color (starts with `#`, `rgb`, `hsl`, or `var(`) +2. If not, looks up the slug in the theme color palette +3. Returns the resolved color value, or the original value if not found + +### Theme Palette Sources + +The resolver checks colors from: +- Theme palette (`theme.json` colors) +- Default WordPress colors +- Custom colors added by the user + +## Example: Advanced Heading Block + +The advanced-heading block has been updated as a reference implementation. See: +- JavaScript: `/src/blocks/blocks/advanced-heading/edit.js` +- PHP: `/inc/css/blocks/class-advanced-heading-css.php` + +## Blocks That Need This Fix + +Based on analysis, these blocks have color attributes and should be updated: + +**High Priority:** +1. Posts Block (multiple color attributes) +2. Section/Column Blocks (backgroundColor, borderColor) +3. Font Awesome Icons Block (textColor, backgroundColor, hover colors) +4. Button Block (color, background, border colors) +5. Review Block (multiple color attributes) + +**Medium Priority:** +6. Popup Block +7. Progress Bar Block +8. Circle Counter Block +9. Countdown Block +10. Sharing Icons Block + +## Testing + +To test color slug resolution: + +1. Create a theme with custom colors in `theme.json`: +```json +{ + "version": 2, + "settings": { + "color": { + "palette": [ + { + "slug": "base", + "color": "#000000", + "name": "Base" + }, + { + "slug": "primary", + "color": "#0073aa", + "name": "Primary" + } + ] + } + } +} +``` + +2. Create a block programmatically with a color slug: +```javascript +wp.data.dispatch('core/block-editor').insertBlocks( + wp.blocks.createBlock('themeisle-blocks/advanced-heading', { + headingColor: "base", + content: "Test" + }) +); +``` + +3. Verify that the correct color is applied both in the editor and on the frontend. + +## Notes + +- This fix is backward compatible - hex, rgb, and other color formats still work +- The resolver only activates when a value looks like a slug (no `#`, `rgb`, etc.) +- The PHP resolver uses `wp_get_global_settings()` which requires WordPress 5.9+ From 2ea551fa05cc3d93b72b80549fe78cda7e07c9e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 12:40:12 +0000 Subject: [PATCH 18/40] Fix trailing whitespace issues from code review Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- inc/class-base-css.php | 2 +- src/blocks/helpers/helper-functions.js | 2 +- src/blocks/helpers/utility-hooks.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/inc/class-base-css.php b/inc/class-base-css.php index 9a2077826..dea361347 100644 --- a/inc/class-base-css.php +++ b/inc/class-base-css.php @@ -230,7 +230,7 @@ public static function resolve_color_value( $value ) { // Try to get the color palette from theme.json. if ( function_exists( 'wp_get_global_settings' ) ) { $global_settings = wp_get_global_settings(); - + // Get colors from different sources (theme, default, custom). $palettes = array(); if ( isset( $global_settings['color']['palette']['theme'] ) ) { diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js index e5b7c8d35..24a0e5619 100644 --- a/src/blocks/helpers/helper-functions.js +++ b/src/blocks/helpers/helper-functions.js @@ -391,7 +391,7 @@ export const resolveColorValue = ( value, palette = null ) => { // Try to find the color in the palette by slug const colorObject = palette.find( color => color.slug === value ); - + // Return the color value if found, otherwise return original value return colorObject?.color || value; }; diff --git a/src/blocks/helpers/utility-hooks.js b/src/blocks/helpers/utility-hooks.js index 6b6ff0a88..f51d45b49 100644 --- a/src/blocks/helpers/utility-hooks.js +++ b/src/blocks/helpers/utility-hooks.js @@ -78,6 +78,6 @@ export const useDarkBackground = ( backgroundColor, attributes, setAttributes, d */ export const useColorResolver = () => { const colorPalette = useSetting( 'color.palette' ) || []; - + return ( colorValue ) => resolveColorValue( colorValue, colorPalette ); }; From e08252c83f5102a2e06fb3bf2c6caedded425961 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:04:09 +0000 Subject: [PATCH 19/40] Apply color slug resolution fix to circle-counter, countdown, sharing-icons, popup, and flip blocks - circle-counter: Added useColorResolver hook and wrapped color attributes in edit.js and CircularProgressBar component; added color resolution in CSS for backgroundColor, progressColor, and titleColor - countdown: Added color resolution in CSS for backgroundColor, borderColor, valueColor, labelColor, and separatorColor - sharing-icons: Added color resolution in CSS for icon backgroundColor and textColor - popup: Added useColorResolver hook and wrapped color attributes in edit.js; added color resolution in CSS for backgroundColor, closeColor, overlayColor, and borderColor - flip: Added color resolution in CSS for borderColor, frontBackgroundColor, backBackgroundColor, titleColor, and descriptionColor All changes follow the pattern of using useColorResolver() in edit.js and Base_CSS::resolve_color_value() in PHP CSS files to properly handle theme color slugs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- inc/css/blocks/class-circle-counter-css.php | 14 ++++++++++---- inc/css/blocks/class-countdown-css.php | 15 +++++++++++++++ inc/css/blocks/class-flip-css.php | 15 +++++++++++++++ inc/css/blocks/class-popup-css.php | 12 ++++++++++++ inc/css/blocks/class-sharing-icons-css.php | 4 ++-- .../components/circular-progress-bar.js | 9 +++++---- src/blocks/blocks/circle-counter/edit.js | 8 ++++++-- src/blocks/blocks/popup/edit.js | 12 +++++++----- 8 files changed, 72 insertions(+), 17 deletions(-) diff --git a/inc/css/blocks/class-circle-counter-css.php b/inc/css/blocks/class-circle-counter-css.php index 0533f3a86..913a79baa 100644 --- a/inc/css/blocks/class-circle-counter-css.php +++ b/inc/css/blocks/class-circle-counter-css.php @@ -67,10 +67,10 @@ public function render_css( $block ) { $percentage = isset( $attrs['percentage'] ) ? $attrs['percentage'] : 50; if ( 50 > $percentage ) { - return isset( $attrs['progressColor'] ) ? $attrs['progressColor'] : '#3878ff'; + return isset( $attrs['progressColor'] ) ? Base_CSS::resolve_color_value( $attrs['progressColor'] ) : '#3878ff'; } - return $value; + return Base_CSS::resolve_color_value( $value ); }, ), array( @@ -80,10 +80,10 @@ public function render_css( $block ) { $percentage = isset( $attrs['percentage'] ) ? $attrs['percentage'] : 50; if ( 50 > $percentage ) { - return isset( $attrs['backgroundColor'] ) ? $attrs['backgroundColor'] : '#4682b426'; + return isset( $attrs['backgroundColor'] ) ? Base_CSS::resolve_color_value( $attrs['backgroundColor'] ) : '#4682b426'; } - return $value; + return Base_CSS::resolve_color_value( $value ); }, ), array( @@ -132,6 +132,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'titleColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), ), ) @@ -144,6 +147,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'progressColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), ), ) diff --git a/inc/css/blocks/class-countdown-css.php b/inc/css/blocks/class-countdown-css.php index d8a5d1373..178eb9b24 100644 --- a/inc/css/blocks/class-countdown-css.php +++ b/inc/css/blocks/class-countdown-css.php @@ -78,10 +78,16 @@ public function render_css( $block ) { array( 'property' => '--background-color', 'value' => 'backgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--border-color', 'value' => 'borderColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--border-style', @@ -279,6 +285,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'valueColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), ), ) @@ -291,6 +300,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'labelColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), ), ) @@ -303,6 +315,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'separatorColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), ), ) diff --git a/inc/css/blocks/class-flip-css.php b/inc/css/blocks/class-flip-css.php index 0332c679b..a06fd8c3d 100644 --- a/inc/css/blocks/class-flip-css.php +++ b/inc/css/blocks/class-flip-css.php @@ -96,6 +96,9 @@ public function render_css( $block ) { array( 'property' => '--border-color', 'value' => 'borderColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--border-width', @@ -136,6 +139,9 @@ public function render_css( $block ) { array( 'property' => '--front-background', 'value' => 'frontBackgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return ! isset( $attrs['frontBackgroundType'] ); }, @@ -198,6 +204,9 @@ public function render_css( $block ) { array( 'property' => '--back-background', 'value' => 'backBackgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return ! isset( $attrs['backBackgroundType'] ); }, @@ -373,6 +382,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'titleColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'font-size', @@ -392,6 +404,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'descriptionColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'font-size', diff --git a/inc/css/blocks/class-popup-css.php b/inc/css/blocks/class-popup-css.php index 75b33ee51..dc9880fc6 100644 --- a/inc/css/blocks/class-popup-css.php +++ b/inc/css/blocks/class-popup-css.php @@ -56,14 +56,23 @@ public function render_css( $block ) { array( 'property' => '--background-color', 'value' => 'backgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--close-color', 'value' => 'closeColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--overlay-color', 'value' => 'overlayColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--overlay-opacity', @@ -89,6 +98,9 @@ public function render_css( $block ) { array( 'property' => '--brd-color', 'value' => 'borderColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--brd-style', diff --git a/inc/css/blocks/class-sharing-icons-css.php b/inc/css/blocks/class-sharing-icons-css.php index 76931d1cc..560c50c2e 100644 --- a/inc/css/blocks/class-sharing-icons-css.php +++ b/inc/css/blocks/class-sharing-icons-css.php @@ -43,7 +43,7 @@ public function render_css( $block ) { 'property' => '--icon-bg-color', 'value' => $icon, 'format' => function ( $value, $attrs ) { - return $value['backgroundColor']; + return Base_CSS::resolve_color_value( $value['backgroundColor'] ); }, 'condition' => function ( $attrs ) use ( $icon ) { return isset( $attrs[ $icon ]['backgroundColor'] ); @@ -53,7 +53,7 @@ public function render_css( $block ) { 'property' => '--text-color', 'value' => $icon, 'format' => function ( $value, $attrs ) { - return $value['textColor']; + return Base_CSS::resolve_color_value( $value['textColor'] ); }, 'condition' => function ( $attrs ) use ( $icon ) { return isset( $attrs[ $icon ]['textColor'] ); diff --git a/src/blocks/blocks/circle-counter/components/circular-progress-bar.js b/src/blocks/blocks/circle-counter/components/circular-progress-bar.js index eb9fbab0e..cd5d6e534 100644 --- a/src/blocks/blocks/circle-counter/components/circular-progress-bar.js +++ b/src/blocks/blocks/circle-counter/components/circular-progress-bar.js @@ -1,7 +1,8 @@ const CircularProgressBar = ({ attributes, progressRef, - valueRef + valueRef, + resolveColor }) => { const size = attributes.height; const center = size / 2; @@ -32,7 +33,7 @@ const CircularProgressBar = ({ r={ radius } strokeWidth={ attributes.strokeWidth } style={ { - stroke: attributes.backgroundColor + stroke: resolveColor( attributes.backgroundColor ) } } /> diff --git a/src/blocks/blocks/circle-counter/edit.js b/src/blocks/blocks/circle-counter/edit.js index c2e263bb8..d2573227d 100644 --- a/src/blocks/blocks/circle-counter/edit.js +++ b/src/blocks/blocks/circle-counter/edit.js @@ -31,6 +31,7 @@ import Inspector from './inspector.js'; import CircularProgressBar from './components/circular-progress-bar.js'; import { blockInit } from '../../helpers/block-utility.js'; import { _px } from '../../helpers/helper-functions'; +import { useColorResolver } from '../../helpers/utility-hooks.js'; const { attributes: defaultAttributes } = metadata; @@ -52,6 +53,8 @@ const CircularProgressBarBlock = ({ return () => unsubscribe( attributes.id ); }, [ attributes.id ]); + const resolveColor = useColorResolver(); + const progressRef = useRef( null ); const valueRef = useRef( null ); const [ interval, changeInterval ] = useState({}); @@ -142,7 +145,7 @@ const CircularProgressBarBlock = ({ onChange={ title => setAttributes({ title }) } multiline={ false } style={ { - color: attributes.titleColor + color: resolveColor( attributes.titleColor ) } } /> @@ -174,6 +177,7 @@ const CircularProgressBarBlock = ({ attributes={ attributes } progressRef={ progressRef } valueRef={ valueRef } + resolveColor={ resolveColor } /> @@ -188,7 +192,7 @@ const CircularProgressBarBlock = ({ onChange={ title => setAttributes({ title }) } multiline={ false } style={ { - color: attributes.titleColor + color: resolveColor( attributes.titleColor ) } } /> diff --git a/src/blocks/blocks/popup/edit.js b/src/blocks/blocks/popup/edit.js index 1a91ffef5..f61bea0dc 100644 --- a/src/blocks/blocks/popup/edit.js +++ b/src/blocks/blocks/popup/edit.js @@ -39,7 +39,7 @@ import metadata from './block.json'; import Inspector from './inspector.js'; import { blockInit, useCSSNode } from '../../helpers/block-utility'; import { boxValues, _cssBlock, stringToBox } from '../../helpers/helper-functions'; -import { useDarkBackground } from '../../helpers/utility-hooks.js'; +import { useDarkBackground, useColorResolver } from '../../helpers/utility-hooks.js'; import { useDispatch, useSelect } from '@wordpress/data'; const { attributes: defaultAttributes } = metadata; @@ -61,6 +61,8 @@ const Edit = ({ return () => unsubscribe( attributes.id ); }, []); + const resolveColor = useColorResolver(); + const [ isEditing, setEditing ] = useState( false ); const { @@ -100,13 +102,13 @@ const Edit = ({ const inlineStyles = { '--min-width': attributes.minWidth ? attributes.minWidth + 'px' : '400px', '--max-width': attributes.maxWidth ? attributes.maxWidth + 'px' : undefined, - '--background-color': attributes.backgroundColor, - '--close-color': attributes.closeColor, - '--overlay-color': attributes.overlayColor, + '--background-color': resolveColor( attributes.backgroundColor ), + '--close-color': resolveColor( attributes.closeColor ), + '--overlay-color': resolveColor( attributes.overlayColor ), '--overlay-opacity': attributes.overlayOpacity !== undefined ? attributes.overlayOpacity / 100 : 1, '--brd-width': boxValues( attributes.borderWidth ), '--brd-radius': boxValues( attributes.borderRadius ), - '--brd-color': attributes.borderColor, + '--brd-color': resolveColor( attributes.borderColor ), '--brd-style': attributes.borderStyle, // Responsive From ff9a9ad809197649c5c01622d90b95e531e539c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:10:45 +0000 Subject: [PATCH 20/40] Apply color slug resolution to high-priority blocks (button, section, icons, posts, review, progress-bar) Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- inc/css/blocks/class-advanced-column-css.php | 3 + inc/css/blocks/class-button-css.php | 36 +++++++++ .../blocks/class-font-awesome-icons-css.php | 39 ++++++++++ inc/css/blocks/class-posts-css.php | 9 +++ inc/css/blocks/class-progress-bar-css.php | 21 ++++++ inc/css/blocks/class-review-css.php | 48 ++++++++++++ inc/css/blocks/class-shared-css.php | 26 ++++++- src/blocks/blocks/button-group/button/edit.js | 73 ++++++++++--------- src/blocks/blocks/font-awesome-icons/edit.js | 22 +++--- src/blocks/blocks/posts/edit.js | 12 ++- src/blocks/blocks/progress-bar/edit.js | 18 +++-- src/blocks/blocks/review/edit.js | 21 +++--- src/blocks/blocks/section/column/edit.js | 22 +++--- src/blocks/blocks/section/columns/edit.js | 20 +++-- 14 files changed, 287 insertions(+), 83 deletions(-) diff --git a/inc/css/blocks/class-advanced-column-css.php b/inc/css/blocks/class-advanced-column-css.php index 1cd53a63d..b152db00c 100644 --- a/inc/css/blocks/class-advanced-column-css.php +++ b/inc/css/blocks/class-advanced-column-css.php @@ -188,6 +188,9 @@ function ( $value ) use ( $block ) { array( 'property' => '--background-color-hover', 'value' => 'backgroundColorHover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'align-self', diff --git a/inc/css/blocks/class-button-css.php b/inc/css/blocks/class-button-css.php index 0b5fdb44e..ec8a67437 100644 --- a/inc/css/blocks/class-button-css.php +++ b/inc/css/blocks/class-button-css.php @@ -135,11 +135,17 @@ public function render_css( $block ) { 'property' => 'color', 'value' => 'color', 'hasSync' => 'gr-btn-color', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'background', 'value' => 'background', 'hasSync' => 'gr-btn-background', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'background', @@ -150,6 +156,9 @@ public function render_css( $block ) { 'property' => 'border-color', 'value' => 'border', 'hasSync' => 'gr-btn-border-color', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'box-shadow', @@ -201,11 +210,17 @@ public function render_css( $block ) { 'property' => 'color', 'value' => 'hoverColor', 'hasSync' => 'gr-btn-color-hover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'background', 'value' => 'hoverBackground', 'hasSync' => 'gr-btn-background-hover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'background', @@ -216,6 +231,9 @@ public function render_css( $block ) { 'property' => 'border-color', 'value' => 'hoverBorder', 'hasSync' => 'gr-btn-border-color-hover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => 'box-shadow', @@ -298,10 +316,16 @@ public function render_global_css() { array( 'property' => '--gr-btn-color', 'value' => 'color', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--gr-btn-background', 'value' => 'background', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--gr-btn-background', @@ -336,6 +360,9 @@ public function render_global_css() { array( 'property' => '--gr-btn-border-color', 'value' => 'border', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return isset( $attrs['border'] ) && ! empty( $attrs['border'] ); }, @@ -421,10 +448,16 @@ public function render_global_css() { array( 'property' => '--gr-btn-color-hover', 'value' => 'hoverColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--gr-btn-background-hover', 'value' => 'hoverBackground', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--gr-btn-background-hover', @@ -433,6 +466,9 @@ public function render_global_css() { array( 'property' => '--gr-btn-border-color-hover', 'value' => 'hoverBorder', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--gr-btn-shadow-hover', diff --git a/inc/css/blocks/class-font-awesome-icons-css.php b/inc/css/blocks/class-font-awesome-icons-css.php index b9381d110..d3008897e 100644 --- a/inc/css/blocks/class-font-awesome-icons-css.php +++ b/inc/css/blocks/class-font-awesome-icons-css.php @@ -86,6 +86,9 @@ public function render_css( $block ) { array( 'property' => '--border-color', 'value' => 'borderColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--border-size', @@ -156,11 +159,17 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'textColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'icon-text-color', ), array( 'property' => 'background', 'value' => 'backgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'icon-background-color', ), ), @@ -174,6 +183,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'textColorHover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return ! ( isset( $attrs['library'] ) && 'themeisle-icons' === $this->get_attr_value( $attrs['library'], 'fontawesome' ) ); }, @@ -182,11 +194,17 @@ public function render_css( $block ) { array( 'property' => 'background', 'value' => 'backgroundColorHover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'icon-background-color-hover', ), array( 'property' => 'border-color', 'value' => 'borderColorHover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'iconBorderColorHover', ), ), @@ -200,6 +218,9 @@ public function render_css( $block ) { array( 'property' => 'color', 'value' => 'textColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return ! ( isset( $attrs['library'] ) && 'themeisle-icons' === $this->get_attr_value( $attrs['library'], 'fontawesome' ) ); }, @@ -216,6 +237,9 @@ public function render_css( $block ) { array( 'property' => 'fill', 'value' => 'textColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return isset( $attrs['library'] ) && 'themeisle-icons' === $this->get_attr_value( $attrs['library'], 'fontawesome' ); }, @@ -232,6 +256,9 @@ public function render_css( $block ) { array( 'property' => 'fill', 'value' => 'textColorHover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return isset( $attrs['library'] ) && 'themeisle-icons' === $this->get_attr_value( $attrs['library'], 'fontawesome' ); }, @@ -297,18 +324,30 @@ public function render_global_css() { array( 'property' => '--icon-text-color-hover', 'value' => 'textColorHover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--icon-background-color-hover', 'value' => 'backgroundColorHover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--icon-text-color', 'value' => 'textColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--icon-background-color', 'value' => 'backgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), ), ) diff --git a/inc/css/blocks/class-posts-css.php b/inc/css/blocks/class-posts-css.php index 377d08a78..19da67532 100644 --- a/inc/css/blocks/class-posts-css.php +++ b/inc/css/blocks/class-posts-css.php @@ -57,10 +57,16 @@ public function render_css( $block ) { array( 'property' => '--text-color', 'value' => 'textColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--background-color', 'value' => 'backgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--background-overlay', @@ -69,6 +75,9 @@ public function render_css( $block ) { array( 'property' => '--border-color', 'value' => 'borderColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--title-text-size', diff --git a/inc/css/blocks/class-progress-bar-css.php b/inc/css/blocks/class-progress-bar-css.php index 363c88fd8..d615a9df3 100644 --- a/inc/css/blocks/class-progress-bar-css.php +++ b/inc/css/blocks/class-progress-bar-css.php @@ -47,10 +47,16 @@ public function render_css( $block ) { array( 'property' => '--title-color', 'value' => 'titleColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--percentage-color', 'value' => 'percentageColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return ! isset( $attrs['percentagePosition'] ); }, @@ -58,6 +64,9 @@ public function render_css( $block ) { array( 'property' => '--percentage-color-outer', 'value' => 'percentageColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return isset( $attrs['percentagePosition'] ) && 'outer' === $attrs['percentagePosition']; }, @@ -65,6 +74,9 @@ public function render_css( $block ) { array( 'property' => '--percentage-color-tooltip', 'value' => 'percentageColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return isset( $attrs['percentagePosition'] ) && 'tooltip' === $attrs['percentagePosition']; }, @@ -72,6 +84,9 @@ public function render_css( $block ) { array( 'property' => '--percentage-color-append', 'value' => 'percentageColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return isset( $attrs['percentagePosition'] ) && 'append' === $attrs['percentagePosition']; }, @@ -79,6 +94,9 @@ public function render_css( $block ) { array( 'property' => '--background-color', 'value' => 'backgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--border-radius', @@ -93,6 +111,9 @@ public function render_css( $block ) { array( 'property' => '--bar-background', 'value' => 'barBackgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--title-font-size', diff --git a/inc/css/blocks/class-review-css.php b/inc/css/blocks/class-review-css.php index 5340a5b73..888a21f70 100644 --- a/inc/css/blocks/class-review-css.php +++ b/inc/css/blocks/class-review-css.php @@ -79,41 +79,65 @@ public function render_css( $block ) { array( 'property' => '--background-color', 'value' => 'backgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'review-background-color', ), array( 'property' => '--primary-color', 'value' => 'primaryColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'review-primary-color', ), array( 'property' => '--text-color', 'value' => 'textColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'review-text-color', ), array( 'property' => '--button-text-color', 'value' => 'buttonTextColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'review-button-text-color', ), array( 'property' => '--border-color', 'value' => 'borderColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'review-border-color', ), array( 'property' => '--stars-color', 'value' => 'starsColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'review-stars-color', ), array( 'property' => '--pros-color', 'value' => 'prosColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'review-pros-color', ), array( 'property' => '--cons-color', 'value' => 'consColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'hasSync' => 'review-cons-color', ), array( @@ -225,34 +249,58 @@ public function render_global_css() { array( 'property' => '--review-background-color', 'value' => 'backgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--review-primary-color', 'value' => 'primaryColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--review-text-color', 'value' => 'textColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--review-button-text-color', 'value' => 'buttonTextColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--review-stars-color', 'value' => 'starsColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--review-border-color', 'value' => 'borderColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--review-pros-color', 'value' => 'prosColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--review-cons-color', 'value' => 'consColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), ), ) diff --git a/inc/css/blocks/class-shared-css.php b/inc/css/blocks/class-shared-css.php index 75a40bcd4..25e7cc98d 100644 --- a/inc/css/blocks/class-shared-css.php +++ b/inc/css/blocks/class-shared-css.php @@ -22,18 +22,30 @@ public static function section_shared() { array( 'property' => '--text-color', 'value' => 'color', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--link-color', 'value' => 'linkColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--content-color-hover', 'value' => 'colorHover', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, ), array( 'property' => '--background', 'value' => 'backgroundColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return ! ( isset( $attrs['backgroundType'] ) && 'color' !== $attrs['backgroundType'] ); }, @@ -209,6 +221,9 @@ public static function section_shared() { array( 'property' => 'border-color', 'value' => 'borderColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return isset( $attrs['border'] ) && is_array( $attrs['border'] ); }, @@ -278,16 +293,18 @@ public static function section_shared() { 'value' => 'boxShadowColor', 'default' => '#000', 'format' => function ( $value, $attrs ) { + $resolved = Base_CSS::resolve_color_value( $value ); + if ( ! isset( $attrs['boxShadowColorOpacity'] ) ) { - return $value; + return $resolved; } if ( 100 === $attrs['boxShadowColorOpacity'] ) { - return $value; + return $resolved; } $opacity = ( isset( $attrs['boxShadowColorOpacity'] ) ? $attrs['boxShadowColorOpacity'] : 50 ) / 100; - return Base_CSS::hex2rgba( $value, $opacity ); + return Base_CSS::hex2rgba( $resolved, $opacity ); }, ), ), @@ -308,6 +325,9 @@ public static function section_overlay() { array( 'property' => 'background', 'value' => 'backgroundOverlayColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, 'condition' => function ( $attrs ) { return ! ( isset( $attrs['backgroundOverlayType'] ) && 'color' !== $attrs['backgroundOverlayType'] ); }, diff --git a/src/blocks/blocks/button-group/button/edit.js b/src/blocks/blocks/button-group/button/edit.js index 0361c8668..9f383fb42 100644 --- a/src/blocks/blocks/button-group/button/edit.js +++ b/src/blocks/blocks/button-group/button/edit.js @@ -33,6 +33,7 @@ import { buildGetSyncValue } from '../../../helpers/block-utility.js'; import { boxToCSS, objectOrNumberAsBox, _cssBlock, _px } from '../../../helpers/helper-functions'; +import { useColorResolver } from '../../../helpers/utility-hooks.js'; const { attributes: defaultAttributes } = metadata; @@ -52,6 +53,9 @@ const Edit = ( props ) => { const getSyncValue = buildGetSyncValue( name, attributes, defaultAttributes ); + // Get color resolver to handle theme color slugs + const resolveColor = useColorResolver(); + useEffect( () => { const unsubscribe = blockInit( clientId, defaultAttributes ); return () => unsubscribe( attributes.id ); @@ -80,40 +84,41 @@ const Edit = ( props ) => {
{ 'none' !== attributes.iconType ? ( diff --git a/src/blocks/blocks/font-awesome-icons/edit.js b/src/blocks/blocks/font-awesome-icons/edit.js index 5fe413a5e..25b35cdfc 100644 --- a/src/blocks/blocks/font-awesome-icons/edit.js +++ b/src/blocks/blocks/font-awesome-icons/edit.js @@ -22,6 +22,7 @@ import Inspector from './inspector.js'; import themeIsleIcons from './../../helpers/themeisle-icons'; import { blockInit, getDefaultValueByField } from '../../helpers/block-utility.js'; import { _cssBlock, boxValues } from '../../helpers/helper-functions'; +import { useColorResolver } from '../../helpers/utility-hooks.js'; const { attributes: defaultAttributes } = metadata; @@ -58,12 +59,15 @@ const Edit = ({ return () => unsubscribe( attributes.id ); }, [ attributes.id ]); + // Get color resolver to handle theme color slugs + const resolveColor = useColorResolver(); + const Icon = themeIsleIcons.icons[ attributes.icon ]; const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes }); const inlineStyles = { - '--border-color': attributes.borderColor, + '--border-color': resolveColor( attributes.borderColor ), '--border-size': attributes.borderSize !== undefined && `${attributes.borderSize }px`, '--border-radius': attributes.borderRadius !== undefined && `${ attributes.borderRadius }%`, '--margin': ! isObjectLike( getValue( 'margin' ) ) ? getValue( 'margin' ) + 'px' : boxValues( getValue( 'margin' ), { top: '5px', right: '5px', bottom: '5px', left: '5px' }), @@ -97,30 +101,30 @@ const Edit = ({ diff --git a/src/blocks/blocks/posts/edit.js b/src/blocks/blocks/posts/edit.js index 73c0d5c5b..12155b196 100644 --- a/src/blocks/blocks/posts/edit.js +++ b/src/blocks/blocks/posts/edit.js @@ -54,7 +54,8 @@ import { } from '../../helpers/helper-functions.js'; import { useDarkBackground, - useResponsiveAttributes + useResponsiveAttributes, + useColorResolver } from '../../helpers/utility-hooks.js'; import '../../components/store/index.js'; import FeaturedPost from './components/layout/featured.js'; @@ -240,6 +241,9 @@ const Edit = ({ useDarkBackground( attributes.backgroundColor, attributes, setAttributes ); + // Get color resolver to handle theme color slugs + const resolveColor = useColorResolver(); + const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes }); const imageBoxShadow = getValue( 'imageBoxShadow' ); @@ -258,10 +262,10 @@ const Edit = ({ '--box-shadow': boxShadow.active && `${ boxShadow.horizontal }px ${ boxShadow.vertical }px ${ boxShadow.blur }px ${ boxShadow.spread }px ${ hex2rgba( boxShadow.color, boxShadow.colorOpacity ) }`, '--vert-align': _align( attributes.verticalAlign ), '--text-align': attributes.textAlign, - '--text-color': attributes.textColor, - '--background-color': attributes.backgroundColor, + '--text-color': resolveColor( attributes.textColor ), + '--background-color': resolveColor( attributes.backgroundColor ), '--background-overlay': attributes.backgroundOverlay || '#0000005e', - '--border-color': attributes.borderColor, + '--border-color': resolveColor( attributes.borderColor ), '--content-gap': attributes.contentGap, '--img-width': responsiveGetAttributes([ _px( attributes.imageWidth ), attributes.imageWidthTablet, attributes.imageWidthMobile ]), '--img-width-tablet': attributes.imageWidthTablet, diff --git a/src/blocks/blocks/progress-bar/edit.js b/src/blocks/blocks/progress-bar/edit.js index 325bcedba..390ba6d5c 100644 --- a/src/blocks/blocks/progress-bar/edit.js +++ b/src/blocks/blocks/progress-bar/edit.js @@ -26,6 +26,7 @@ import { import metadata from './block.json'; import { blockInit } from '../../helpers/block-utility.js'; import Inspector from './inspector.js'; +import { useColorResolver } from '../../helpers/utility-hooks.js'; const { attributes: defaultAttributes } = metadata; @@ -46,6 +47,9 @@ const ProgressBar = ({ return () => unsubscribe( attributes.id ); }, [ attributes.id ]); + // Get color resolver to handle theme color slugs + const resolveColor = useColorResolver(); + const blockRef = useRef( null ); const [ showPercentage, setShowPercentage ] = useState( false ); @@ -86,15 +90,15 @@ const ProgressBar = ({ }, [ attributes.percentage, attributes.duration ]); const inlineStyles = { - '--title-color': attributes.titleColor, - '--percentage-color': attributes.percentageColor, - '--percentage-color-outer': attributes.percentageColor, - '--percentage-color-tooltip': attributes.percentageColor, - '--percentage-color-append': attributes.percentageColor, - '--background-color': attributes.backgroundColor, + '--title-color': resolveColor( attributes.titleColor ), + '--percentage-color': resolveColor( attributes.percentageColor ), + '--percentage-color-outer': resolveColor( attributes.percentageColor ), + '--percentage-color-tooltip': resolveColor( attributes.percentageColor ), + '--percentage-color-append': resolveColor( attributes.percentageColor ), + '--background-color': resolveColor( attributes.backgroundColor ), '--border-radius': attributes.borderRadius !== undefined && ( attributes.borderRadius + 'px' ), '--height': attributes.height !== undefined && ( attributes.height + 'px' ), - '--bar-background': attributes.barBackgroundColor, + '--bar-background': resolveColor( attributes.barBackgroundColor ), '--title-font-size': attributes.titleFontSize }; diff --git a/src/blocks/blocks/review/edit.js b/src/blocks/blocks/review/edit.js index c0cdca204..3ab458661 100644 --- a/src/blocks/blocks/review/edit.js +++ b/src/blocks/blocks/review/edit.js @@ -47,7 +47,7 @@ import { getDefaultValueByField } from '../../helpers/block-utility.js'; -import { useDarkBackground } from '../../helpers/utility-hooks.js'; +import { useDarkBackground, useColorResolver } from '../../helpers/utility-hooks.js'; import { _px } from '../../helpers/helper-functions'; const { attributes: defaultAttributes } = metadata; @@ -116,6 +116,9 @@ const Edit = ({ const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes }); + // Get color resolver to handle theme color slugs + const resolveColor = useColorResolver(); + useDarkBackground( getValue( 'backgroundColor' ), attributes, setAttributes ); const overallRatings = ( attributes.features.reduce( ( accumulator, feature ) => accumulator + feature.rating, 0 ) / attributes.features.length ).toFixed( 1 ); @@ -153,14 +156,14 @@ const Edit = ({ const boxShadow = getValue( 'boxShadow' ); const inlineStyles = { - '--background-color': getValue( 'backgroundColor' ), - '--primary-color': getValue( 'primaryColor' ), - '--text-color': getValue( 'textColor' ), - '--button-text-color': getValue( 'buttonTextColor' ), - '--border-color': getValue( 'borderColor' ), - '--stars-color': getValue( 'starsColor' ), - '--pros-color': getValue( 'prosColor' ), - '--cons-color': getValue( 'consColor' ), + '--background-color': resolveColor( getValue( 'backgroundColor' ) ), + '--primary-color': resolveColor( getValue( 'primaryColor' ) ), + '--text-color': resolveColor( getValue( 'textColor' ) ), + '--button-text-color': resolveColor( getValue( 'buttonTextColor' ) ), + '--border-color': resolveColor( getValue( 'borderColor' ) ), + '--stars-color': resolveColor( getValue( 'starsColor' ) ), + '--pros-color': resolveColor( getValue( 'prosColor' ) ), + '--cons-color': resolveColor( getValue( 'consColor' ) ), '--content-font-size': getValue( 'contentFontSize' ), ...( attributes?.padding?.top && { '--padding-desktop-top': attributes.padding.top }), ...( attributes?.padding?.bottom && { '--padding-desktop-bottom': attributes.padding.bottom }), diff --git a/src/blocks/blocks/section/column/edit.js b/src/blocks/blocks/section/column/edit.js index 295a705f5..03c62322e 100644 --- a/src/blocks/blocks/section/column/edit.js +++ b/src/blocks/blocks/section/column/edit.js @@ -38,7 +38,7 @@ import { getDefaultValueByField } from '../../../helpers/block-utility.js'; import { _cssBlock } from '../../../helpers/helper-functions'; -import { useDarkBackground } from '../../../helpers/utility-hooks.js'; +import { useDarkBackground, useColorResolver } from '../../../helpers/utility-hooks.js'; const { attributes: defaultAttributes } = metadata; @@ -104,6 +104,9 @@ const Edit = ({ return () => unsubscribe( attributes.id ); }, [ attributes.id ]); + // Get color resolver to handle theme color slugs + const resolveColor = useColorResolver(); + useEffect( () => { if ( 1 < parentBlock.innerBlocks.length ) { if ( ! adjacentBlockClientId ) { @@ -223,7 +226,7 @@ const Edit = ({ if ( 'color' === attributes.backgroundType ) { background = { - '--background': attributes.backgroundColor + '--background': resolveColor( attributes.backgroundColor ) }; } @@ -250,7 +253,7 @@ const Edit = ({ borderBottomWidth: attributes.border.bottom, borderLeftWidth: attributes.border.left, borderStyle: 'solid', - borderColor: attributes.borderColor + borderColor: resolveColor( attributes.borderColor ) }; } @@ -264,8 +267,9 @@ const Edit = ({ } if ( true === attributes.boxShadow ) { + const resolvedBoxShadowColor = resolveColor( attributes.boxShadowColor ); boxShadowStyle = { - boxShadow: `${ attributes.boxShadowHorizontal }px ${ attributes.boxShadowVertical }px ${ attributes.boxShadowBlur }px ${ attributes.boxShadowSpread }px ${ attributes.boxShadowColor.includes( 'var(' ) && ( attributes.boxShadowColorOpacity === undefined || 100 === attributes.boxShadowColorOpacity ) ? attributes.boxShadowColor : hexToRgba( ( attributes.boxShadowColor ? attributes.boxShadowColor : '#000000' ), attributes.boxShadowColorOpacity ) }` + boxShadow: `${ attributes.boxShadowHorizontal }px ${ attributes.boxShadowVertical }px ${ attributes.boxShadowBlur }px ${ attributes.boxShadowSpread }px ${ resolvedBoxShadowColor?.includes( 'var(' ) && ( attributes.boxShadowColorOpacity === undefined || 100 === attributes.boxShadowColorOpacity ) ? resolvedBoxShadowColor : hexToRgba( ( resolvedBoxShadowColor || '#000000' ), attributes.boxShadowColorOpacity ) }` }; } @@ -281,8 +285,8 @@ const Edit = ({ ...borderStyle, ...borderRadiusStyle, ...boxShadowStyle, - '--link-color': attributes.linkColor, - '--background-color-hover': attributes.backgroundColorHover + '--link-color': resolveColor( attributes.linkColor ), + '--background-color-hover': resolveColor( attributes.backgroundColorHover ) }; if ( attributes.verticalAlign ) { @@ -291,7 +295,7 @@ const Edit = ({ if ( 'color' === attributes.backgroundOverlayType ) { overlayBackground = { - background: attributes.backgroundOverlayColor, + background: resolveColor( attributes.backgroundOverlayColor ), opacity: attributes.backgroundOverlayOpacity / 100 }; } @@ -333,12 +337,12 @@ const Edit = ({ diff --git a/src/blocks/blocks/section/columns/edit.js b/src/blocks/blocks/section/columns/edit.js index 8c2aaeb2d..78e0a9dbd 100644 --- a/src/blocks/blocks/section/columns/edit.js +++ b/src/blocks/blocks/section/columns/edit.js @@ -54,7 +54,7 @@ import { } from '../../../helpers/block-utility.js'; import { columnsIcon as icon } from '../../../helpers/icons.js'; import { _cssBlock, _px } from '../../../helpers/helper-functions'; -import { useDarkBackground } from '../../../helpers/utility-hooks.js'; +import { useDarkBackground, useColorResolver } from '../../../helpers/utility-hooks.js'; const { attributes: defaultAttributes } = metadata; @@ -74,6 +74,9 @@ const Edit = ({ return () => unsubscribe( attributes.id ); }, [ attributes.id ]); + // Get color resolver to handle theme color slugs + const resolveColor = useColorResolver(); + const { updateBlockAttributes, replaceInnerBlocks } = useDispatch( 'core/block-editor' ); const { @@ -266,7 +269,7 @@ const Edit = ({ if ( 'color' === attributes.backgroundType ) { background = { - backgroundColor: attributes.backgroundColor + backgroundColor: resolveColor( attributes.backgroundColor ) }; } @@ -293,7 +296,7 @@ const Edit = ({ borderBottomWidth: attributes.border.bottom, borderLeftWidth: attributes.border.left, borderStyle: 'solid', - borderColor: attributes.borderColor + borderColor: resolveColor( attributes.borderColor ) }; } @@ -307,8 +310,9 @@ const Edit = ({ } if ( true === attributes.boxShadow ) { + const resolvedBoxShadowColor = resolveColor( attributes.boxShadowColor ); boxShadowStyle = { - boxShadow: `${ attributes.boxShadowHorizontal }px ${ attributes.boxShadowVertical }px ${ attributes.boxShadowBlur }px ${ attributes.boxShadowSpread }px ${ attributes.boxShadowColor.includes( 'var(' ) && ( attributes.boxShadowColorOpacity === undefined || 100 === attributes.boxShadowColorOpacity ) ? attributes.boxShadowColor : hexToRgba( ( attributes.boxShadowColor ? attributes.boxShadowColor : '#000000' ), attributes.boxShadowColorOpacity ) }` + boxShadow: `${ attributes.boxShadowHorizontal }px ${ attributes.boxShadowVertical }px ${ attributes.boxShadowBlur }px ${ attributes.boxShadowSpread }px ${ resolvedBoxShadowColor?.includes( 'var(' ) && ( attributes.boxShadowColorOpacity === undefined || 100 === attributes.boxShadowColorOpacity ) ? resolvedBoxShadowColor : hexToRgba( ( resolvedBoxShadowColor || '#000000' ), attributes.boxShadowColorOpacity ) }` }; } @@ -318,12 +322,12 @@ const Edit = ({ ...borderStyle, ...borderRadiusStyle, ...boxShadowStyle, - '--link-color': attributes.linkColor + '--link-color': resolveColor( attributes.linkColor ) }; if ( 'color' === attributes.backgroundOverlayType ) { overlayBackground = { - background: attributes.backgroundOverlayColor, + background: resolveColor( attributes.backgroundOverlayColor ), opacity: attributes.backgroundOverlayOpacity / 100 }; } @@ -420,12 +424,12 @@ const Edit = ({ From 013c0dabf4f7d639aaf07ab63ff3731811021976 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:49:37 +0000 Subject: [PATCH 21/40] Use CSS variables to preserve color slug connection to theme.json Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- docs/color-slug-resolution.md | 96 ++++++++++++++++++++------ inc/class-base-css.php | 69 ++++++++---------- src/blocks/helpers/helper-functions.js | 53 +++++++------- 3 files changed, 134 insertions(+), 84 deletions(-) diff --git a/docs/color-slug-resolution.md b/docs/color-slug-resolution.md index 5c6fe0c52..262d863a0 100644 --- a/docs/color-slug-resolution.md +++ b/docs/color-slug-resolution.md @@ -2,13 +2,33 @@ ## Problem -WordPress themes define color palettes in `theme.json` using color slugs (e.g., "primary", "base", "contrast"). Core WordPress blocks can reference these colors by slug, and they are automatically resolved to the actual color values. +WordPress themes define color palettes in `theme.json` using color slugs (e.g., "primary", "base", "contrast"). Core WordPress blocks can reference these colors by slug, and they automatically update when theme colors change. -However, Otter Blocks were not resolving these color slugs, causing them to revert to defaults when a slug was used instead of a hex/rgb value. +Otter Blocks were not properly handling color slugs - they were converting slugs to hex values at render time, which broke the connection to theme.json. When theme colors changed, blocks didn't update. ## Solution -We've added color slug resolution utilities that work on both the JavaScript (editor) and PHP (frontend) sides. +We've implemented the same approach as WordPress core blocks: **using CSS variables to preserve the connection to theme.json**. + +### How It Works + +WordPress automatically generates CSS variables from `theme.json` color palette: +```css +/* WordPress generates these automatically */ +:root { + --wp--preset--color--primary: #0073aa; + --wp--preset--color--base: #000000; + --wp--preset--color--contrast: #ffffff; +} +``` + +When you use a color slug in Otter blocks, it's converted to a CSS variable reference: +```javascript +// Input: slug "primary" +// Output: "var(--wp--preset--color--primary)" +``` + +**Key Benefit:** When theme.json changes, the CSS variable value updates automatically, and all blocks using that slug instantly reflect the new color - no block re-save needed! ### JavaScript Solution @@ -21,7 +41,9 @@ const Edit = ({ attributes, setAttributes }) => { // Get the color resolver function const resolveColor = useColorResolver(); - // Use it to resolve any color attribute + // Converts slugs to CSS variables + // "primary" → "var(--wp--preset--color--primary)" + // "#ff0000" → "#ff0000" (hex values passed through) const resolvedBackgroundColor = resolveColor(attributes.backgroundColor); const resolvedTextColor = resolveColor(attributes.textColor); @@ -35,20 +57,23 @@ const Edit = ({ attributes, setAttributes }) => { }; ``` -#### Option 2: Use `resolveColorValue` Directly +#### Option 2: Use `resolveColorValue` or `getColorCSSVariable` Directly ```javascript -import { useSetting } from '@wordpress/block-editor'; -import { resolveColorValue } from '../../helpers/helper-functions'; +import { resolveColorValue, getColorCSSVariable } from '../../helpers/helper-functions'; const Edit = ({ attributes, setAttributes }) => { - // Get the color palette - const colorPalette = useSetting('color.palette') || []; + // Both functions do the same thing - convert slug to CSS variable + const colorVar1 = resolveColorValue(attributes.backgroundColor); + const colorVar2 = getColorCSSVariable(attributes.textColor); - // Resolve colors - const resolvedColor = resolveColorValue(attributes.backgroundColor, colorPalette); + // "primary" → "var(--wp--preset--color--primary)" + // "#ff0000" → "#ff0000" - return
...
; + return
...
; }; ``` @@ -79,21 +104,50 @@ $css->add_item( ); ``` +**Result:** +- Input slug: `"primary"` +- Output CSS: `background-color: var(--wp--preset--color--primary);` +- Input hex: `"#ff0000"` +- Output CSS: `background-color: #ff0000;` + ## How It Works ### Color Resolution Logic -The resolver: +The resolver converts color slugs to CSS variables: 1. Checks if the value is already a color (starts with `#`, `rgb`, `hsl`, or `var(`) -2. If not, looks up the slug in the theme color palette -3. Returns the resolved color value, or the original value if not found +2. If it's a hex/rgb/hsl value, returns it unchanged +3. If it's a slug (anything else), converts to CSS variable: `var(--wp--preset--color--{slug})` + +**No palette lookup needed!** WordPress handles the CSS variable definitions automatically. -### Theme Palette Sources +### WordPress CSS Variable Generation + +WordPress reads `theme.json` and automatically generates CSS variables: + +```json +// theme.json +{ + "settings": { + "color": { + "palette": [ + { "slug": "primary", "color": "#0073aa", "name": "Primary" }, + { "slug": "base", "color": "#000000", "name": "Base" } + ] + } + } +} +``` + +WordPress outputs: +```css +:root { + --wp--preset--color--primary: #0073aa; + --wp--preset--color--base: #000000; +} +``` -The resolver checks colors from: -- Theme palette (`theme.json` colors) -- Default WordPress colors -- Custom colors added by the user +When you change colors in `theme.json`, WordPress updates the CSS variables, and all blocks automatically reflect the change! ## Example: Advanced Heading Block @@ -101,7 +155,7 @@ The advanced-heading block has been updated as a reference implementation. See: - JavaScript: `/src/blocks/blocks/advanced-heading/edit.js` - PHP: `/inc/css/blocks/class-advanced-heading-css.php` -## Blocks That Need This Fix +## Blocks Updated Based on analysis, these blocks have color attributes and should be updated: diff --git a/inc/class-base-css.php b/inc/class-base-css.php index dea361347..614f5d02c 100644 --- a/inc/class-base-css.php +++ b/inc/class-base-css.php @@ -203,56 +203,47 @@ public static function hex2rgba( $color, $opacity = false ) { } /** - * Resolve a color value which may be a slug from the theme color palette. - * If the value is a slug (doesn't start with # or rgb/hsl/var), attempts to resolve it from the palette. - * Otherwise, returns the value as-is. + * Convert a color slug to a CSS variable reference. + * WordPress generates CSS variables in the format: --wp--preset--color--{slug} * - * @param string|null $value The color value or slug. - * @return string|null The resolved color value. + * @param string|null $slug The color slug. + * @return string|null The CSS variable reference. * @since 3.1.5 * @access public */ - public static function resolve_color_value( $value ) { - if ( empty( $value ) ) { - return $value; + public static function get_color_css_variable( $slug ) { + if ( empty( $slug ) ) { + return $slug; } - // If it's already a color value (hex, rgb, hsl, var), return as-is. + // If it's already a color value or CSS variable, return as-is. if ( - strpos( $value, '#' ) === 0 || - strpos( $value, 'rgb' ) === 0 || - strpos( $value, 'hsl' ) === 0 || - strpos( $value, 'var(' ) === 0 + strpos( $slug, '#' ) === 0 || + strpos( $slug, 'rgb' ) === 0 || + strpos( $slug, 'hsl' ) === 0 || + strpos( $slug, 'var(' ) === 0 ) { - return $value; + return $slug; } - // Try to get the color palette from theme.json. - if ( function_exists( 'wp_get_global_settings' ) ) { - $global_settings = wp_get_global_settings(); - - // Get colors from different sources (theme, default, custom). - $palettes = array(); - if ( isset( $global_settings['color']['palette']['theme'] ) ) { - $palettes = array_merge( $palettes, $global_settings['color']['palette']['theme'] ); - } - if ( isset( $global_settings['color']['palette']['default'] ) ) { - $palettes = array_merge( $palettes, $global_settings['color']['palette']['default'] ); - } - if ( isset( $global_settings['color']['palette']['custom'] ) ) { - $palettes = array_merge( $palettes, $global_settings['color']['palette']['custom'] ); - } - - // Try to find the color by slug. - foreach ( $palettes as $color_obj ) { - if ( isset( $color_obj['slug'] ) && $color_obj['slug'] === $value ) { - return $color_obj['color']; - } - } - } + // Convert slug to CSS variable. + return 'var(--wp--preset--color--' . $slug . ')'; + } - // If we couldn't resolve it, return the original value. - return $value; + /** + * Resolve a color value which may be a slug from the theme color palette. + * This function converts slugs to CSS variables to preserve the connection to theme.json. + * If the value is a slug, it returns a CSS variable reference. + * Otherwise, returns the value as-is (for hex, rgb, hsl values). + * + * @param string|null $value The color value or slug. + * @return string|null The CSS variable or color value. + * @since 3.1.5 + * @access public + */ + public static function resolve_color_value( $value ) { + // Use CSS variable conversion for slugs. + return self::get_color_css_variable( $value ); } /** diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js index 24a0e5619..c2a524e48 100644 --- a/src/blocks/helpers/helper-functions.js +++ b/src/blocks/helpers/helper-functions.js @@ -361,39 +361,44 @@ export const lightnessFromColor = color => { }; /** - * Resolve a color value which may be a slug from the color palette. - * If the value is a slug (doesn't start with # or rgb/hsl/var), attempts to resolve it from the palette. - * Otherwise, returns the value as-is. + * Convert a color slug to a CSS variable reference. + * WordPress generates CSS variables in the format: --wp--preset--color--{slug} * - * @param {string|undefined} value The color value or slug - * @param {Array} palette Optional color palette array from useSetting('color.palette') - * @return {string|undefined} The resolved color value + * @param {string|undefined} slug The color slug + * @return {string|undefined} The CSS variable reference */ -export const resolveColorValue = ( value, palette = null ) => { - if ( ! value ) { - return value; +export const getColorCSSVariable = ( slug ) => { + if ( ! slug ) { + return slug; } - // If it's already a color value (hex, rgb, hsl, var), return as-is + // If it's already a color value or CSS variable, return as-is if ( - value.startsWith( '#' ) || - value.startsWith( 'rgb' ) || - value.startsWith( 'hsl' ) || - value.startsWith( 'var(' ) + slug.startsWith( '#' ) || + slug.startsWith( 'rgb' ) || + slug.startsWith( 'hsl' ) || + slug.startsWith( 'var(' ) ) { - return value; + return slug; } - // If no palette provided, we can't resolve, so return the value - if ( ! palette || ! Array.isArray( palette ) ) { - return value; - } - - // Try to find the color in the palette by slug - const colorObject = palette.find( color => color.slug === value ); + // Convert slug to CSS variable + return `var(--wp--preset--color--${ slug })`; +}; - // Return the color value if found, otherwise return original value - return colorObject?.color || value; +/** + * Resolve a color value which may be a slug from the color palette. + * This function converts slugs to CSS variables to preserve the connection to theme.json. + * If the value is a slug, it returns a CSS variable reference. + * Otherwise, returns the value as-is (for hex, rgb, hsl values). + * + * @param {string|undefined} value The color value or slug + * @param {Array} palette Optional color palette array (for backwards compatibility, not used) + * @return {string|undefined} The CSS variable or color value + */ +export const resolveColorValue = ( value, palette = null ) => { + // Use CSS variable conversion for slugs + return getColorCSSVariable( value ); }; /** From c4f3f7028697863a01d385c7168c3846c1a1bd38 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:51:32 +0000 Subject: [PATCH 22/40] Add slug sanitization and improve documentation clarity Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- docs/color-slug-resolution.md | 16 +++++++--------- inc/class-base-css.php | 6 +++++- src/blocks/helpers/helper-functions.js | 9 +++++++-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/color-slug-resolution.md b/docs/color-slug-resolution.md index 262d863a0..70596a63f 100644 --- a/docs/color-slug-resolution.md +++ b/docs/color-slug-resolution.md @@ -57,26 +57,24 @@ const Edit = ({ attributes, setAttributes }) => { }; ``` -#### Option 2: Use `resolveColorValue` or `getColorCSSVariable` Directly +#### Option 2: Use `getColorCSSVariable` Directly (Recommended for New Code) ```javascript -import { resolveColorValue, getColorCSSVariable } from '../../helpers/helper-functions'; +import { getColorCSSVariable } from '../../helpers/helper-functions'; const Edit = ({ attributes, setAttributes }) => { - // Both functions do the same thing - convert slug to CSS variable - const colorVar1 = resolveColorValue(attributes.backgroundColor); - const colorVar2 = getColorCSSVariable(attributes.textColor); + // Direct conversion of slug to CSS variable + const colorVar = getColorCSSVariable(attributes.backgroundColor); // "primary" → "var(--wp--preset--color--primary)" // "#ff0000" → "#ff0000" - return
...
; + return
...
; }; ``` +**Note:** `resolveColorValue()` is also available as a wrapper for backward compatibility, but `getColorCSSVariable()` is recommended for new code as it's more explicit about its purpose. + ### PHP Solution In your block's CSS class (e.g., `class-my-block-css.php`), use the `Base_CSS::resolve_color_value()` method: diff --git a/inc/class-base-css.php b/inc/class-base-css.php index 614f5d02c..9200278ce 100644 --- a/inc/class-base-css.php +++ b/inc/class-base-css.php @@ -226,8 +226,12 @@ public static function get_color_css_variable( $slug ) { return $slug; } + // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric and hyphens. + // This prevents potential CSS injection if slug comes from untrusted sources. + $sanitized_slug = strtolower( preg_replace( '/[^a-z0-9-]/', '', $slug ) ); + // Convert slug to CSS variable. - return 'var(--wp--preset--color--' . $slug . ')'; + return 'var(--wp--preset--color--' . $sanitized_slug . ')'; } /** diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js index c2a524e48..ecd714434 100644 --- a/src/blocks/helpers/helper-functions.js +++ b/src/blocks/helpers/helper-functions.js @@ -382,8 +382,12 @@ export const getColorCSSVariable = ( slug ) => { return slug; } + // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric and hyphens + // This prevents potential CSS injection if slug comes from untrusted sources + const sanitizedSlug = slug.toLowerCase().replace( /[^a-z0-9-]/g, '' ); + // Convert slug to CSS variable - return `var(--wp--preset--color--${ slug })`; + return `var(--wp--preset--color--${ sanitizedSlug })`; }; /** @@ -393,8 +397,9 @@ export const getColorCSSVariable = ( slug ) => { * Otherwise, returns the value as-is (for hex, rgb, hsl values). * * @param {string|undefined} value The color value or slug - * @param {Array} palette Optional color palette array (for backwards compatibility, not used) + * @param {Array} palette Optional color palette array (deprecated, no longer used) * @return {string|undefined} The CSS variable or color value + * @deprecated The palette parameter is deprecated and no longer used. Use getColorCSSVariable() directly. */ export const resolveColorValue = ( value, palette = null ) => { // Use CSS variable conversion for slugs From 75d0ccb6d41bb7dbe20869686e18bdd92d0c8481 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:53:52 +0000 Subject: [PATCH 23/40] Add comprehensive implementation summary documentation Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- docs/IMPLEMENTATION_SUMMARY.md | 288 +++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 docs/IMPLEMENTATION_SUMMARY.md diff --git a/docs/IMPLEMENTATION_SUMMARY.md b/docs/IMPLEMENTATION_SUMMARY.md new file mode 100644 index 000000000..f74c057ce --- /dev/null +++ b/docs/IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,288 @@ +# Color Slug Implementation Summary + +## Overview + +This document summarizes the implementation of CSS variable-based color slug handling in Otter Blocks, matching the approach used by WordPress core blocks. + +## Problem + +**Previous Implementation:** +- Color slugs (e.g., `"primary"`, `"base"`) were resolved to actual hex values at render time +- Example: `"primary"` → `"#0073aa"` +- **Issue:** When theme.json colors changed, blocks didn't update because they were using hardcoded hex values +- Connection to theme.json was lost after initial render + +**User Impact:** +- Changing theme colors required re-saving all blocks +- Blocks didn't automatically adapt to theme changes +- Inconsistent with WordPress core block behavior + +## Solution + +**New Implementation:** +- Color slugs are converted to CSS variables that reference the theme palette +- Example: `"primary"` → `"var(--wp--preset--color--primary)"` +- **Benefit:** WordPress automatically updates CSS variable values when theme.json changes +- All blocks instantly reflect new colors without re-saving + +## Technical Details + +### JavaScript Implementation + +**File:** `src/blocks/helpers/helper-functions.js` + +**New Function:** +```javascript +export const getColorCSSVariable = ( slug ) => { + if ( ! slug ) return slug; + + // Pass through existing color values + if (slug.startsWith('#') || slug.startsWith('rgb') || + slug.startsWith('hsl') || slug.startsWith('var(')) { + return slug; + } + + // Sanitize and convert slug to CSS variable + const sanitizedSlug = slug.toLowerCase().replace(/[^a-z0-9-]/g, ''); + return `var(--wp--preset--color--${sanitizedSlug})`; +}; +``` + +**Updated Function:** +```javascript +export const resolveColorValue = ( value, palette = null ) => { + // Now uses CSS variable conversion + return getColorCSSVariable( value ); +}; +``` + +### PHP Implementation + +**File:** `inc/class-base-css.php` + +**New Method:** +```php +public static function get_color_css_variable( $slug ) { + if ( empty( $slug ) ) return $slug; + + // Pass through existing color values + if (strpos($slug, '#') === 0 || strpos($slug, 'rgb') === 0 || + strpos($slug, 'hsl') === 0 || strpos($slug, 'var(') === 0) { + return $slug; + } + + // Sanitize and convert slug to CSS variable + $sanitized_slug = strtolower(preg_replace('/[^a-z0-9-]/', '', $slug)); + return 'var(--wp--preset--color--' . $sanitized_slug . ')'; +} +``` + +**Updated Method:** +```php +public static function resolve_color_value( $value ) { + // Now uses CSS variable conversion + return self::get_color_css_variable( $value ); +} +``` + +## Security Considerations + +### Slug Sanitization + +Both JavaScript and PHP implementations sanitize slugs to prevent CSS injection: + +**Rules:** +- Convert to lowercase +- Allow only: `a-z`, `0-9`, `-` (hyphen) +- Remove all other characters + +**Examples:** +- `"Primary-Color_123"` → `"primary-color123"` +- `"test@color!"` → `"testcolor"` +- `"base"` → `"base"` (unchanged) + +**Why:** Prevents potential CSS injection if slug values come from untrusted sources. + +## How WordPress Generates CSS Variables + +When you define colors in `theme.json`: + +```json +{ + "settings": { + "color": { + "palette": [ + { "slug": "primary", "color": "#0073aa", "name": "Primary" }, + { "slug": "base", "color": "#000000", "name": "Base" } + ] + } + } +} +``` + +WordPress automatically generates CSS in the document ``: + +```css +:root { + --wp--preset--color--primary: #0073aa; + --wp--preset--color--base: #000000; +} +``` + +## Usage Examples + +### JavaScript (Block Edit Component) + +```javascript +import { useColorResolver } from '../../helpers/utility-hooks.js'; + +const Edit = ({ attributes }) => { + const resolveColor = useColorResolver(); + + // Converts slugs to CSS variables + const style = { + color: resolveColor(attributes.headingColor), + background: resolveColor(attributes.backgroundColor) + }; + + return
...
; +}; +``` + +**Input/Output:** +- `attributes.headingColor = "primary"` → `style.color = "var(--wp--preset--color--primary)"` +- `attributes.backgroundColor = "#ff0000"` → `style.background = "#ff0000"` + +### PHP (CSS Generation) + +```php +$css->add_item( + array( + 'properties' => array( + array( + 'property' => 'color', + 'value' => 'headingColor', + 'format' => function ( $value ) { + return Base_CSS::resolve_color_value( $value ); + }, + ), + ), + ) +); +``` + +**Generated CSS:** +- Input: `headingColor = "primary"` → Output: `color: var(--wp--preset--color--primary);` +- Input: `headingColor = "#ff0000"` → Output: `color: #ff0000;` + +## Backward Compatibility + +The implementation is fully backward compatible: + +| Input Type | Example | Output | Notes | +|------------|---------|--------|-------| +| Color Slug | `"primary"` | `"var(--wp--preset--color--primary)"` | New behavior | +| Hex Value | `"#ff0000"` | `"#ff0000"` | Unchanged | +| RGB Value | `"rgb(255,0,0)"` | `"rgb(255,0,0)"` | Unchanged | +| HSL Value | `"hsl(0,100%,50%)"` | `"hsl(0,100%,50%)"` | Unchanged | +| CSS Variable | `"var(--custom)"` | `"var(--custom)"` | Unchanged | + +## Affected Blocks + +All 15 Otter blocks with color attributes automatically benefit from this change: + +1. advanced-heading +2. button-group/button +3. section/column +4. section/columns +5. font-awesome-icons +6. posts +7. review +8. progress-bar +9. circle-counter +10. countdown +11. sharing-icons +12. popup +13. flip +14. lottie +15. modal + +## Testing + +### Automated Tests + +```javascript +// Test cases verified: +✓ "primary" → "var(--wp--preset--color--primary)" +✓ "base" → "var(--wp--preset--color--base)" +✓ "#ff0000" → "#ff0000" +✓ "rgb(255, 0, 0)" → "rgb(255, 0, 0)" +✓ "var(--custom-color)" → "var(--custom-color)" +✓ "Primary-Color_123" → "var(--wp--preset--color--primary-color123)" +✓ "test@color!" → "var(--wp--preset--color--testcolor)" +``` + +### Manual Testing Steps + +1. **Setup:** + - Create/edit a theme with custom colors in `theme.json` + - Add Otter blocks using color slugs + +2. **Test Color Slug:** + ```javascript + wp.data.dispatch('core/block-editor').insertBlocks( + wp.blocks.createBlock('themeisle-blocks/advanced-heading', { + headingColor: "primary", + content: "Test Heading" + }) + ); + ``` + +3. **Verify:** + - Check that block displays correctly in editor + - Inspect CSS to see `var(--wp--preset--color--primary)` + +4. **Test Theme Change:** + - Change the `primary` color value in `theme.json` + - Refresh page + - Verify block color updates automatically (no re-save needed) + +5. **Test Backward Compatibility:** + - Use hex value: `headingColor: "#ff0000"` + - Verify it still works correctly + +## Benefits + +1. **Auto-updates:** Theme color changes apply instantly to all blocks +2. **No re-save needed:** Blocks maintain connection to theme palette +3. **Core consistency:** Matches WordPress core block behavior +4. **Maintainability:** Easier theme customization and testing +5. **Performance:** No palette lookup needed at runtime +6. **Security:** Slug sanitization prevents CSS injection + +## Migration Notes + +**For Existing Blocks:** +- No migration needed! Existing hex/rgb values continue to work +- New blocks using slugs will automatically use CSS variables +- Old blocks can be updated by changing to slug references + +**For Developers:** +- Use `getColorCSSVariable()` for new code (explicit) +- Use `useColorResolver()` hook in React components (convenient) +- `resolveColorValue()` maintained for backward compatibility + +## Related Files + +- `src/blocks/helpers/helper-functions.js` - Core conversion logic +- `src/blocks/helpers/utility-hooks.js` - React hook wrapper +- `inc/class-base-css.php` - PHP conversion logic +- `docs/color-slug-resolution.md` - User documentation +- All block `edit.js` files - Using the utilities +- All block CSS PHP files - Using the utilities + +## References + +- [WordPress Block Editor Handbook - Global Settings](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles/) +- [WordPress Block Supports](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/) +- [theme.json Reference](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/) From ad981f16c6c527cdacaa03667b09af7e62062cac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:55:49 +0000 Subject: [PATCH 24/40] Remove extra documentation and add unit tests for color CSS variable functions Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- docs/IMPLEMENTATION_SUMMARY.md | 288 ------------------ src/blocks/test/unit/helper-functions.test.ts | 79 ++++- 2 files changed, 78 insertions(+), 289 deletions(-) delete mode 100644 docs/IMPLEMENTATION_SUMMARY.md diff --git a/docs/IMPLEMENTATION_SUMMARY.md b/docs/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index f74c057ce..000000000 --- a/docs/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,288 +0,0 @@ -# Color Slug Implementation Summary - -## Overview - -This document summarizes the implementation of CSS variable-based color slug handling in Otter Blocks, matching the approach used by WordPress core blocks. - -## Problem - -**Previous Implementation:** -- Color slugs (e.g., `"primary"`, `"base"`) were resolved to actual hex values at render time -- Example: `"primary"` → `"#0073aa"` -- **Issue:** When theme.json colors changed, blocks didn't update because they were using hardcoded hex values -- Connection to theme.json was lost after initial render - -**User Impact:** -- Changing theme colors required re-saving all blocks -- Blocks didn't automatically adapt to theme changes -- Inconsistent with WordPress core block behavior - -## Solution - -**New Implementation:** -- Color slugs are converted to CSS variables that reference the theme palette -- Example: `"primary"` → `"var(--wp--preset--color--primary)"` -- **Benefit:** WordPress automatically updates CSS variable values when theme.json changes -- All blocks instantly reflect new colors without re-saving - -## Technical Details - -### JavaScript Implementation - -**File:** `src/blocks/helpers/helper-functions.js` - -**New Function:** -```javascript -export const getColorCSSVariable = ( slug ) => { - if ( ! slug ) return slug; - - // Pass through existing color values - if (slug.startsWith('#') || slug.startsWith('rgb') || - slug.startsWith('hsl') || slug.startsWith('var(')) { - return slug; - } - - // Sanitize and convert slug to CSS variable - const sanitizedSlug = slug.toLowerCase().replace(/[^a-z0-9-]/g, ''); - return `var(--wp--preset--color--${sanitizedSlug})`; -}; -``` - -**Updated Function:** -```javascript -export const resolveColorValue = ( value, palette = null ) => { - // Now uses CSS variable conversion - return getColorCSSVariable( value ); -}; -``` - -### PHP Implementation - -**File:** `inc/class-base-css.php` - -**New Method:** -```php -public static function get_color_css_variable( $slug ) { - if ( empty( $slug ) ) return $slug; - - // Pass through existing color values - if (strpos($slug, '#') === 0 || strpos($slug, 'rgb') === 0 || - strpos($slug, 'hsl') === 0 || strpos($slug, 'var(') === 0) { - return $slug; - } - - // Sanitize and convert slug to CSS variable - $sanitized_slug = strtolower(preg_replace('/[^a-z0-9-]/', '', $slug)); - return 'var(--wp--preset--color--' . $sanitized_slug . ')'; -} -``` - -**Updated Method:** -```php -public static function resolve_color_value( $value ) { - // Now uses CSS variable conversion - return self::get_color_css_variable( $value ); -} -``` - -## Security Considerations - -### Slug Sanitization - -Both JavaScript and PHP implementations sanitize slugs to prevent CSS injection: - -**Rules:** -- Convert to lowercase -- Allow only: `a-z`, `0-9`, `-` (hyphen) -- Remove all other characters - -**Examples:** -- `"Primary-Color_123"` → `"primary-color123"` -- `"test@color!"` → `"testcolor"` -- `"base"` → `"base"` (unchanged) - -**Why:** Prevents potential CSS injection if slug values come from untrusted sources. - -## How WordPress Generates CSS Variables - -When you define colors in `theme.json`: - -```json -{ - "settings": { - "color": { - "palette": [ - { "slug": "primary", "color": "#0073aa", "name": "Primary" }, - { "slug": "base", "color": "#000000", "name": "Base" } - ] - } - } -} -``` - -WordPress automatically generates CSS in the document ``: - -```css -:root { - --wp--preset--color--primary: #0073aa; - --wp--preset--color--base: #000000; -} -``` - -## Usage Examples - -### JavaScript (Block Edit Component) - -```javascript -import { useColorResolver } from '../../helpers/utility-hooks.js'; - -const Edit = ({ attributes }) => { - const resolveColor = useColorResolver(); - - // Converts slugs to CSS variables - const style = { - color: resolveColor(attributes.headingColor), - background: resolveColor(attributes.backgroundColor) - }; - - return
...
; -}; -``` - -**Input/Output:** -- `attributes.headingColor = "primary"` → `style.color = "var(--wp--preset--color--primary)"` -- `attributes.backgroundColor = "#ff0000"` → `style.background = "#ff0000"` - -### PHP (CSS Generation) - -```php -$css->add_item( - array( - 'properties' => array( - array( - 'property' => 'color', - 'value' => 'headingColor', - 'format' => function ( $value ) { - return Base_CSS::resolve_color_value( $value ); - }, - ), - ), - ) -); -``` - -**Generated CSS:** -- Input: `headingColor = "primary"` → Output: `color: var(--wp--preset--color--primary);` -- Input: `headingColor = "#ff0000"` → Output: `color: #ff0000;` - -## Backward Compatibility - -The implementation is fully backward compatible: - -| Input Type | Example | Output | Notes | -|------------|---------|--------|-------| -| Color Slug | `"primary"` | `"var(--wp--preset--color--primary)"` | New behavior | -| Hex Value | `"#ff0000"` | `"#ff0000"` | Unchanged | -| RGB Value | `"rgb(255,0,0)"` | `"rgb(255,0,0)"` | Unchanged | -| HSL Value | `"hsl(0,100%,50%)"` | `"hsl(0,100%,50%)"` | Unchanged | -| CSS Variable | `"var(--custom)"` | `"var(--custom)"` | Unchanged | - -## Affected Blocks - -All 15 Otter blocks with color attributes automatically benefit from this change: - -1. advanced-heading -2. button-group/button -3. section/column -4. section/columns -5. font-awesome-icons -6. posts -7. review -8. progress-bar -9. circle-counter -10. countdown -11. sharing-icons -12. popup -13. flip -14. lottie -15. modal - -## Testing - -### Automated Tests - -```javascript -// Test cases verified: -✓ "primary" → "var(--wp--preset--color--primary)" -✓ "base" → "var(--wp--preset--color--base)" -✓ "#ff0000" → "#ff0000" -✓ "rgb(255, 0, 0)" → "rgb(255, 0, 0)" -✓ "var(--custom-color)" → "var(--custom-color)" -✓ "Primary-Color_123" → "var(--wp--preset--color--primary-color123)" -✓ "test@color!" → "var(--wp--preset--color--testcolor)" -``` - -### Manual Testing Steps - -1. **Setup:** - - Create/edit a theme with custom colors in `theme.json` - - Add Otter blocks using color slugs - -2. **Test Color Slug:** - ```javascript - wp.data.dispatch('core/block-editor').insertBlocks( - wp.blocks.createBlock('themeisle-blocks/advanced-heading', { - headingColor: "primary", - content: "Test Heading" - }) - ); - ``` - -3. **Verify:** - - Check that block displays correctly in editor - - Inspect CSS to see `var(--wp--preset--color--primary)` - -4. **Test Theme Change:** - - Change the `primary` color value in `theme.json` - - Refresh page - - Verify block color updates automatically (no re-save needed) - -5. **Test Backward Compatibility:** - - Use hex value: `headingColor: "#ff0000"` - - Verify it still works correctly - -## Benefits - -1. **Auto-updates:** Theme color changes apply instantly to all blocks -2. **No re-save needed:** Blocks maintain connection to theme palette -3. **Core consistency:** Matches WordPress core block behavior -4. **Maintainability:** Easier theme customization and testing -5. **Performance:** No palette lookup needed at runtime -6. **Security:** Slug sanitization prevents CSS injection - -## Migration Notes - -**For Existing Blocks:** -- No migration needed! Existing hex/rgb values continue to work -- New blocks using slugs will automatically use CSS variables -- Old blocks can be updated by changing to slug references - -**For Developers:** -- Use `getColorCSSVariable()` for new code (explicit) -- Use `useColorResolver()` hook in React components (convenient) -- `resolveColorValue()` maintained for backward compatibility - -## Related Files - -- `src/blocks/helpers/helper-functions.js` - Core conversion logic -- `src/blocks/helpers/utility-hooks.js` - React hook wrapper -- `inc/class-base-css.php` - PHP conversion logic -- `docs/color-slug-resolution.md` - User documentation -- All block `edit.js` files - Using the utilities -- All block CSS PHP files - Using the utilities - -## References - -- [WordPress Block Editor Handbook - Global Settings](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles/) -- [WordPress Block Supports](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/) -- [theme.json Reference](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/) diff --git a/src/blocks/test/unit/helper-functions.test.ts b/src/blocks/test/unit/helper-functions.test.ts index c4db851be..5114758cb 100644 --- a/src/blocks/test/unit/helper-functions.test.ts +++ b/src/blocks/test/unit/helper-functions.test.ts @@ -1,4 +1,4 @@ -import { boxToCSS, boxValues, buildResponsiveGetAttributes, buildResponsiveSetAttributes, compactObject, getChoice, mergeBoxDefaultValues, removeBoxDefaultValues, stringToBox } from '../../helpers/helper-functions.js'; +import { boxToCSS, boxValues, buildResponsiveGetAttributes, buildResponsiveSetAttributes, compactObject, getChoice, getColorCSSVariable, mergeBoxDefaultValues, removeBoxDefaultValues, resolveColorValue, stringToBox } from '../../helpers/helper-functions.js'; describe( 'Box Values Function', () => { @@ -239,3 +239,80 @@ describe( 'Compact Object Function', () => { expect( compactObject({ a: {}, b: {}}) ).toBeUndefined(); }); }); + +describe( 'Get Color CSS Variable Function', () => { + it( 'should convert a color slug to a CSS variable', () => { + expect( getColorCSSVariable( 'primary' ) ).toEqual( 'var(--wp--preset--color--primary)' ); + expect( getColorCSSVariable( 'base' ) ).toEqual( 'var(--wp--preset--color--base)' ); + expect( getColorCSSVariable( 'contrast' ) ).toEqual( 'var(--wp--preset--color--contrast)' ); + }); + + it( 'should pass through hex color values unchanged', () => { + expect( getColorCSSVariable( '#ff0000' ) ).toEqual( '#ff0000' ); + expect( getColorCSSVariable( '#0073aa' ) ).toEqual( '#0073aa' ); + expect( getColorCSSVariable( '#000' ) ).toEqual( '#000' ); + }); + + it( 'should pass through rgb color values unchanged', () => { + expect( getColorCSSVariable( 'rgb(255, 0, 0)' ) ).toEqual( 'rgb(255, 0, 0)' ); + expect( getColorCSSVariable( 'rgba(255, 0, 0, 0.5)' ) ).toEqual( 'rgba(255, 0, 0, 0.5)' ); + }); + + it( 'should pass through hsl color values unchanged', () => { + expect( getColorCSSVariable( 'hsl(0, 100%, 50%)' ) ).toEqual( 'hsl(0, 100%, 50%)' ); + expect( getColorCSSVariable( 'hsla(0, 100%, 50%, 0.5)' ) ).toEqual( 'hsla(0, 100%, 50%, 0.5)' ); + }); + + it( 'should pass through existing CSS variables unchanged', () => { + expect( getColorCSSVariable( 'var(--custom-color)' ) ).toEqual( 'var(--custom-color)' ); + expect( getColorCSSVariable( 'var(--my-brand-color)' ) ).toEqual( 'var(--my-brand-color)' ); + }); + + it( 'should sanitize color slugs to prevent CSS injection', () => { + expect( getColorCSSVariable( 'Primary-Color' ) ).toEqual( 'var(--wp--preset--color--primary-color)' ); + expect( getColorCSSVariable( 'test@color!' ) ).toEqual( 'var(--wp--preset--color--testcolor)' ); + expect( getColorCSSVariable( 'my_special_color' ) ).toEqual( 'var(--wp--preset--color--myspecialcolor)' ); + expect( getColorCSSVariable( 'Color123' ) ).toEqual( 'var(--wp--preset--color--color123)' ); + }); + + it( 'should handle undefined and empty values', () => { + expect( getColorCSSVariable( undefined ) ).toBeUndefined(); + expect( getColorCSSVariable( '' ) ).toEqual( '' ); + }); + + it( 'should preserve hyphens in slugs', () => { + expect( getColorCSSVariable( 'primary-blue' ) ).toEqual( 'var(--wp--preset--color--primary-blue)' ); + expect( getColorCSSVariable( 'dark-gray-100' ) ).toEqual( 'var(--wp--preset--color--dark-gray-100)' ); + }); +}); + +describe( 'Resolve Color Value Function', () => { + it( 'should convert color slugs to CSS variables', () => { + expect( resolveColorValue( 'primary' ) ).toEqual( 'var(--wp--preset--color--primary)' ); + expect( resolveColorValue( 'base' ) ).toEqual( 'var(--wp--preset--color--base)' ); + }); + + it( 'should pass through hex values unchanged', () => { + expect( resolveColorValue( '#ff0000' ) ).toEqual( '#ff0000' ); + expect( resolveColorValue( '#0073aa' ) ).toEqual( '#0073aa' ); + }); + + it( 'should pass through rgb values unchanged', () => { + expect( resolveColorValue( 'rgb(255, 0, 0)' ) ).toEqual( 'rgb(255, 0, 0)' ); + }); + + it( 'should work with deprecated palette parameter (ignored)', () => { + const palette = [ + { slug: 'primary', color: '#0073aa', name: 'Primary' }, + { slug: 'base', color: '#000000', name: 'Base' } + ]; + + // Palette parameter is now ignored, should still convert to CSS variable + expect( resolveColorValue( 'primary', palette ) ).toEqual( 'var(--wp--preset--color--primary)' ); + expect( resolveColorValue( 'base', palette ) ).toEqual( 'var(--wp--preset--color--base)' ); + }); + + it( 'should handle undefined values', () => { + expect( resolveColorValue( undefined ) ).toBeUndefined(); + }); +}); From cd5785030237831afb727a53e94f88cd83817f30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:58:01 +0000 Subject: [PATCH 25/40] Allow underscores in color slug sanitization per WordPress conventions Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- inc/class-base-css.php | 4 ++-- src/blocks/helpers/helper-functions.js | 4 ++-- src/blocks/test/unit/helper-functions.test.ts | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/inc/class-base-css.php b/inc/class-base-css.php index 9200278ce..8d804cc3b 100644 --- a/inc/class-base-css.php +++ b/inc/class-base-css.php @@ -226,9 +226,9 @@ public static function get_color_css_variable( $slug ) { return $slug; } - // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric and hyphens. + // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric, hyphens, and underscores. // This prevents potential CSS injection if slug comes from untrusted sources. - $sanitized_slug = strtolower( preg_replace( '/[^a-z0-9-]/', '', $slug ) ); + $sanitized_slug = strtolower( preg_replace( '/[^a-z0-9-_]/', '', $slug ) ); // Convert slug to CSS variable. return 'var(--wp--preset--color--' . $sanitized_slug . ')'; diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js index ecd714434..26debde47 100644 --- a/src/blocks/helpers/helper-functions.js +++ b/src/blocks/helpers/helper-functions.js @@ -382,9 +382,9 @@ export const getColorCSSVariable = ( slug ) => { return slug; } - // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric and hyphens + // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric, hyphens, and underscores // This prevents potential CSS injection if slug comes from untrusted sources - const sanitizedSlug = slug.toLowerCase().replace( /[^a-z0-9-]/g, '' ); + const sanitizedSlug = slug.toLowerCase().replace( /[^a-z0-9-_]/g, '' ); // Convert slug to CSS variable return `var(--wp--preset--color--${ sanitizedSlug })`; diff --git a/src/blocks/test/unit/helper-functions.test.ts b/src/blocks/test/unit/helper-functions.test.ts index 5114758cb..c679a1b73 100644 --- a/src/blocks/test/unit/helper-functions.test.ts +++ b/src/blocks/test/unit/helper-functions.test.ts @@ -271,7 +271,7 @@ describe( 'Get Color CSS Variable Function', () => { it( 'should sanitize color slugs to prevent CSS injection', () => { expect( getColorCSSVariable( 'Primary-Color' ) ).toEqual( 'var(--wp--preset--color--primary-color)' ); expect( getColorCSSVariable( 'test@color!' ) ).toEqual( 'var(--wp--preset--color--testcolor)' ); - expect( getColorCSSVariable( 'my_special_color' ) ).toEqual( 'var(--wp--preset--color--myspecialcolor)' ); + expect( getColorCSSVariable( 'my_special_color' ) ).toEqual( 'var(--wp--preset--color--my_special_color)' ); expect( getColorCSSVariable( 'Color123' ) ).toEqual( 'var(--wp--preset--color--color123)' ); }); @@ -280,9 +280,11 @@ describe( 'Get Color CSS Variable Function', () => { expect( getColorCSSVariable( '' ) ).toEqual( '' ); }); - it( 'should preserve hyphens in slugs', () => { + it( 'should preserve hyphens and underscores in slugs', () => { expect( getColorCSSVariable( 'primary-blue' ) ).toEqual( 'var(--wp--preset--color--primary-blue)' ); expect( getColorCSSVariable( 'dark-gray-100' ) ).toEqual( 'var(--wp--preset--color--dark-gray-100)' ); + expect( getColorCSSVariable( 'secondary_dark' ) ).toEqual( 'var(--wp--preset--color--secondary_dark)' ); + expect( getColorCSSVariable( 'accent_1' ) ).toEqual( 'var(--wp--preset--color--accent_1)' ); }); }); From 890c765094ef355ac92045bbd0d65a3138f545bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:10:08 +0000 Subject: [PATCH 26/40] Remove color-slug-resolution.md documentation file Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- docs/color-slug-resolution.md | 217 ---------------------------------- 1 file changed, 217 deletions(-) delete mode 100644 docs/color-slug-resolution.md diff --git a/docs/color-slug-resolution.md b/docs/color-slug-resolution.md deleted file mode 100644 index 70596a63f..000000000 --- a/docs/color-slug-resolution.md +++ /dev/null @@ -1,217 +0,0 @@ -# Color Slug Resolution in Otter Blocks - -## Problem - -WordPress themes define color palettes in `theme.json` using color slugs (e.g., "primary", "base", "contrast"). Core WordPress blocks can reference these colors by slug, and they automatically update when theme colors change. - -Otter Blocks were not properly handling color slugs - they were converting slugs to hex values at render time, which broke the connection to theme.json. When theme colors changed, blocks didn't update. - -## Solution - -We've implemented the same approach as WordPress core blocks: **using CSS variables to preserve the connection to theme.json**. - -### How It Works - -WordPress automatically generates CSS variables from `theme.json` color palette: -```css -/* WordPress generates these automatically */ -:root { - --wp--preset--color--primary: #0073aa; - --wp--preset--color--base: #000000; - --wp--preset--color--contrast: #ffffff; -} -``` - -When you use a color slug in Otter blocks, it's converted to a CSS variable reference: -```javascript -// Input: slug "primary" -// Output: "var(--wp--preset--color--primary)" -``` - -**Key Benefit:** When theme.json changes, the CSS variable value updates automatically, and all blocks using that slug instantly reflect the new color - no block re-save needed! - -### JavaScript Solution - -#### Option 1: Use the `useColorResolver` Hook (Recommended) - -```javascript -import { useColorResolver } from '../../helpers/utility-hooks.js'; - -const Edit = ({ attributes, setAttributes }) => { - // Get the color resolver function - const resolveColor = useColorResolver(); - - // Converts slugs to CSS variables - // "primary" → "var(--wp--preset--color--primary)" - // "#ff0000" → "#ff0000" (hex values passed through) - const resolvedBackgroundColor = resolveColor(attributes.backgroundColor); - const resolvedTextColor = resolveColor(attributes.textColor); - - // Apply to styles - const style = { - backgroundColor: resolvedBackgroundColor, - color: resolvedTextColor - }; - - return
...
; -}; -``` - -#### Option 2: Use `getColorCSSVariable` Directly (Recommended for New Code) - -```javascript -import { getColorCSSVariable } from '../../helpers/helper-functions'; - -const Edit = ({ attributes, setAttributes }) => { - // Direct conversion of slug to CSS variable - const colorVar = getColorCSSVariable(attributes.backgroundColor); - - // "primary" → "var(--wp--preset--color--primary)" - // "#ff0000" → "#ff0000" - - return
...
; -}; -``` - -**Note:** `resolveColorValue()` is also available as a wrapper for backward compatibility, but `getColorCSSVariable()` is recommended for new code as it's more explicit about its purpose. - -### PHP Solution - -In your block's CSS class (e.g., `class-my-block-css.php`), use the `Base_CSS::resolve_color_value()` method: - -```php -$css->add_item( - array( - 'properties' => array( - array( - 'property' => 'background-color', - 'value' => 'backgroundColor', - 'format' => function ( $value, $attrs ) { - return Base_CSS::resolve_color_value( $value ); - }, - ), - array( - 'property' => 'color', - 'value' => 'textColor', - 'format' => function ( $value, $attrs ) { - return Base_CSS::resolve_color_value( $value ); - }, - ), - ), - ) -); -``` - -**Result:** -- Input slug: `"primary"` -- Output CSS: `background-color: var(--wp--preset--color--primary);` -- Input hex: `"#ff0000"` -- Output CSS: `background-color: #ff0000;` - -## How It Works - -### Color Resolution Logic - -The resolver converts color slugs to CSS variables: -1. Checks if the value is already a color (starts with `#`, `rgb`, `hsl`, or `var(`) -2. If it's a hex/rgb/hsl value, returns it unchanged -3. If it's a slug (anything else), converts to CSS variable: `var(--wp--preset--color--{slug})` - -**No palette lookup needed!** WordPress handles the CSS variable definitions automatically. - -### WordPress CSS Variable Generation - -WordPress reads `theme.json` and automatically generates CSS variables: - -```json -// theme.json -{ - "settings": { - "color": { - "palette": [ - { "slug": "primary", "color": "#0073aa", "name": "Primary" }, - { "slug": "base", "color": "#000000", "name": "Base" } - ] - } - } -} -``` - -WordPress outputs: -```css -:root { - --wp--preset--color--primary: #0073aa; - --wp--preset--color--base: #000000; -} -``` - -When you change colors in `theme.json`, WordPress updates the CSS variables, and all blocks automatically reflect the change! - -## Example: Advanced Heading Block - -The advanced-heading block has been updated as a reference implementation. See: -- JavaScript: `/src/blocks/blocks/advanced-heading/edit.js` -- PHP: `/inc/css/blocks/class-advanced-heading-css.php` - -## Blocks Updated - -Based on analysis, these blocks have color attributes and should be updated: - -**High Priority:** -1. Posts Block (multiple color attributes) -2. Section/Column Blocks (backgroundColor, borderColor) -3. Font Awesome Icons Block (textColor, backgroundColor, hover colors) -4. Button Block (color, background, border colors) -5. Review Block (multiple color attributes) - -**Medium Priority:** -6. Popup Block -7. Progress Bar Block -8. Circle Counter Block -9. Countdown Block -10. Sharing Icons Block - -## Testing - -To test color slug resolution: - -1. Create a theme with custom colors in `theme.json`: -```json -{ - "version": 2, - "settings": { - "color": { - "palette": [ - { - "slug": "base", - "color": "#000000", - "name": "Base" - }, - { - "slug": "primary", - "color": "#0073aa", - "name": "Primary" - } - ] - } - } -} -``` - -2. Create a block programmatically with a color slug: -```javascript -wp.data.dispatch('core/block-editor').insertBlocks( - wp.blocks.createBlock('themeisle-blocks/advanced-heading', { - headingColor: "base", - content: "Test" - }) -); -``` - -3. Verify that the correct color is applied both in the editor and on the frontend. - -## Notes - -- This fix is backward compatible - hex, rgb, and other color formats still work -- The resolver only activates when a value looks like a slug (no `#`, `rgb`, etc.) -- The PHP resolver uses `wp_get_global_settings()` which requires WordPress 5.9+ From 8079ae9687e01cfa4722807bb6b493e791abdc9d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:17:23 +0000 Subject: [PATCH 27/40] Fix JSDoc alignment error in helper-functions.js Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- src/blocks/helpers/helper-functions.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js index 26debde47..f1fb41892 100644 --- a/src/blocks/helpers/helper-functions.js +++ b/src/blocks/helpers/helper-functions.js @@ -396,10 +396,10 @@ export const getColorCSSVariable = ( slug ) => { * If the value is a slug, it returns a CSS variable reference. * Otherwise, returns the value as-is (for hex, rgb, hsl values). * - * @param {string|undefined} value The color value or slug - * @param {Array} palette Optional color palette array (deprecated, no longer used) - * @return {string|undefined} The CSS variable or color value * @deprecated The palette parameter is deprecated and no longer used. Use getColorCSSVariable() directly. + * @param {string|undefined} value The color value or slug + * @param {Array} palette Optional color palette array (deprecated, no longer used) + * @return {string|undefined} The CSS variable or color value */ export const resolveColorValue = ( value, palette = null ) => { // Use CSS variable conversion for slugs From e6c672ce6306a7119abbf9a9097ec92da77430a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:54:08 +0000 Subject: [PATCH 28/40] Fix useDarkBackground to handle color slugs properly Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- src/blocks/helpers/utility-hooks.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/blocks/helpers/utility-hooks.js b/src/blocks/helpers/utility-hooks.js index f51d45b49..23c57a860 100644 --- a/src/blocks/helpers/utility-hooks.js +++ b/src/blocks/helpers/utility-hooks.js @@ -10,7 +10,7 @@ import { useSetting } from '@wordpress/block-editor'; /** * Internal dependencies */ -import { buildResponsiveGetAttributes, buildResponsiveSetAttributes, lightnessFromColor, resolveColorValue } from './helper-functions.js'; +import { buildResponsiveGetAttributes, buildResponsiveSetAttributes, lightnessFromColor, resolveColorValue, getColorCSSVariable } from './helper-functions.js'; /** * Utiliy hook to get/set responsive attributes. @@ -39,8 +39,10 @@ export const useResponsiveAttributes = ( setAttributes = () => {}) => useSelect( */ export const useDarkBackground = ( backgroundColor, attributes, setAttributes, darkClassName = 'has-dark-bg', lightClassName = 'has-light-bg' ) => { useEffect( () => { - const isDark = 'dark' === lightnessFromColor( backgroundColor ); - const isLight = 'light' === lightnessFromColor( backgroundColor ); + // Resolve color slugs to CSS variables before checking lightness + const resolvedColor = getColorCSSVariable( backgroundColor ); + const isDark = 'dark' === lightnessFromColor( resolvedColor ); + const isLight = 'light' === lightnessFromColor( resolvedColor ); let classes = attributes.className || ''; From b66e4561fcfe09762b46afb38c3ef78f33488e3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 21:24:28 +0000 Subject: [PATCH 29/40] Add robust type checking and error handling to color functions Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- src/blocks/helpers/helper-functions.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js index f1fb41892..871f88a9e 100644 --- a/src/blocks/helpers/helper-functions.js +++ b/src/blocks/helpers/helper-functions.js @@ -331,14 +331,22 @@ export const hex2rgba = ( color, alpha = 100 ) => { * @return {string|boolean} */ export const lightnessFromColor = color => { - if ( ! color ) { + // Handle falsy values and non-string types + if ( ! color || typeof color !== 'string' ) { return false; } let value = color; + // Handle CSS variables if ( color.startsWith( 'var(' ) ) { - value = getComputedStyle( document.documentElement ).getPropertyValue( color.slice( 4, -1 ) ).trim(); + // Check if we're in a browser environment + if ( typeof document !== 'undefined' && document.documentElement ) { + value = getComputedStyle( document.documentElement ).getPropertyValue( color.slice( 4, -1 ) ).trim(); + } else { + // In test/non-browser environment, can't resolve CSS variables + return false; + } } // Convert hex to RGB if necessary @@ -351,7 +359,13 @@ export const lightnessFromColor = color => { } // Extract the red, green, and blue values - const [ r, g, b ] = value.match( /\d+/g ).map( Number ); + const matches = value.match( /\d+/g ); + if ( ! matches || matches.length < 3 ) { + // Invalid color format + return false; + } + + const [ r, g, b ] = matches.map( Number ); // Calculate the brightness value const brightness = ( 0.299 * r ) + ( 0.587 * g ) + ( 0.114 * b ); @@ -368,7 +382,8 @@ export const lightnessFromColor = color => { * @return {string|undefined} The CSS variable reference */ export const getColorCSSVariable = ( slug ) => { - if ( ! slug ) { + // Handle all falsy values and non-string types + if ( ! slug || typeof slug !== 'string' ) { return slug; } From 90a6c997354c616c85d13d2da97010d26cf182de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:07:05 +0000 Subject: [PATCH 30/40] Simplify useColorResolver hook - remove unused palette dependency Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- src/blocks/helpers/utility-hooks.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/blocks/helpers/utility-hooks.js b/src/blocks/helpers/utility-hooks.js index 23c57a860..d09217571 100644 --- a/src/blocks/helpers/utility-hooks.js +++ b/src/blocks/helpers/utility-hooks.js @@ -5,12 +5,10 @@ import { useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; -import { useSetting } from '@wordpress/block-editor'; - /** * Internal dependencies */ -import { buildResponsiveGetAttributes, buildResponsiveSetAttributes, lightnessFromColor, resolveColorValue, getColorCSSVariable } from './helper-functions.js'; +import { buildResponsiveGetAttributes, buildResponsiveSetAttributes, lightnessFromColor, getColorCSSVariable } from './helper-functions.js'; /** * Utiliy hook to get/set responsive attributes. @@ -74,12 +72,13 @@ export const useDarkBackground = ( backgroundColor, attributes, setAttributes, d /** * Utility hook to resolve color slugs from the theme palette. - * Returns a function that can resolve a color value (which may be a slug) to its actual color. + * Returns a function that can resolve a color value (which may be a slug) to a CSS variable. + * This preserves the connection to theme.json colors. * * @return {Function} A function that resolves color values/slugs. */ export const useColorResolver = () => { - const colorPalette = useSetting( 'color.palette' ) || []; - - return ( colorValue ) => resolveColorValue( colorValue, colorPalette ); + // Return the getColorCSSVariable function directly + // We don't need the palette since we use CSS variables + return getColorCSSVariable; }; From 243efd229bd2793f4cee9b5ff1a1b48caa07285a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:11:39 +0000 Subject: [PATCH 31/40] Fix accordion and tabs blocks to use color slug resolution Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- src/blocks/blocks/accordion/group/edit.js | 15 ++++++++------- src/blocks/blocks/tabs/group/edit.js | 21 ++++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/blocks/blocks/accordion/group/edit.js b/src/blocks/blocks/accordion/group/edit.js index 5399ca46a..63e3e186e 100644 --- a/src/blocks/blocks/accordion/group/edit.js +++ b/src/blocks/blocks/accordion/group/edit.js @@ -25,7 +25,7 @@ import { useCSSNode } from '../../../helpers/block-utility.js'; -import { useDarkBackground } from '../../../helpers/utility-hooks.js'; +import { useDarkBackground, useColorResolver } from '../../../helpers/utility-hooks.js'; // @ts-ignore import faIcons from '../../../../../assets/fontawesome/fa-icons.json'; @@ -70,12 +70,13 @@ const Edit = ({ }, []); const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes }); + const resolveColor = useColorResolver(); const inlineStyles = { - '--title-color': getValue( 'titleColor' ), - '--title-background': getValue( 'titleBackground' ), - '--content-background': getValue( 'contentBackground' ), - '--border-color': getValue( 'borderColor' ), + '--title-color': resolveColor( getValue( 'titleColor' ) ), + '--title-background': resolveColor( getValue( 'titleBackground' ) ), + '--content-background': resolveColor( getValue( 'contentBackground' ) ), + '--border-color': resolveColor( getValue( 'borderColor' ) ), '--border-width': getValue( 'borderWidth' ), '--box-shadow': attributes.boxShadow.active && `${attributes.boxShadow.horizontal}px ${attributes.boxShadow.vertical}px ${attributes.boxShadow.blur}px ${attributes.boxShadow.spread}px ${hex2rgba( attributes.boxShadow.color, attributes.boxShadow.colorOpacity )}`, '--padding': boxValues( attributes.padding, { top: '18px', right: '24px', bottom: '18px', left: '24px' }), @@ -119,8 +120,8 @@ const Edit = ({ const [ activeCSSNodeName, setActiveNodeCSS ] = useCSSNode(); useEffect( () => { - const activeTitleColor = getValue( 'activeTitleColor' ); - const activeTitleBackground = getValue( 'activeTitleBackground' ); + const activeTitleColor = resolveColor( getValue( 'activeTitleColor' ) ); + const activeTitleBackground = resolveColor( getValue( 'activeTitleBackground' ) ); setActiveNodeCSS([ ...( activeTitleColor ? [ `> * > * > .wp-block-themeisle-blocks-accordion-item.is-open > .wp-block-themeisle-blocks-accordion-item__title { diff --git a/src/blocks/blocks/tabs/group/edit.js b/src/blocks/blocks/tabs/group/edit.js index 7e942713a..2cd7fc5e2 100644 --- a/src/blocks/blocks/tabs/group/edit.js +++ b/src/blocks/blocks/tabs/group/edit.js @@ -38,7 +38,7 @@ import Controls from './controls.js'; import { blockInit, getDefaultValueByField } from '../../../helpers/block-utility.js'; import { boxToCSS, objectOrNumberAsBox, _px } from '../../../helpers/helper-functions'; import BlockAppender from '../../../components/block-appender-button'; -import { useDarkBackground } from '../../../helpers/utility-hooks.js'; +import { useDarkBackground, useColorResolver } from '../../../helpers/utility-hooks.js'; const { attributes: defaultAttributes } = metadata; @@ -168,6 +168,9 @@ const Edit = ({ useDarkBackground( attributes.tabColor, attributes, setAttributes ); + // Get color resolver to handle theme color slugs + const resolveColor = useColorResolver(); + /** * ------------ Tab Actions ------------ */ @@ -213,14 +216,14 @@ const Edit = ({ const inlineStyles = { '--title-border-width': boxToCSS( getSyncValue( 'titleBorderWidth' ) ), '--border-width': boxToCSS( _px( getSyncValue( 'borderWidth' ) ) ), - '--border-color': getSyncValue( 'borderColor' ), - '--title-color': getSyncValue( 'titleColor' ), - '--title-background': getSyncValue( 'titleBackgroundColor' ), - '--tab-color': getSyncValue( 'tabColor' ), - '--active-title-color': getSyncValue( 'activeTitleColor' ), - '--active-title-background': getSyncValue( 'activeTitleBackgroundColor' ), - '--active-title-border-color': getSyncValue( 'activeBorderColor' ), - '--content-text-color': getSyncValue( 'contentTextColor' ), + '--border-color': resolveColor( getSyncValue( 'borderColor' ) ), + '--title-color': resolveColor( getSyncValue( 'titleColor' ) ), + '--title-background': resolveColor( getSyncValue( 'titleBackgroundColor' ) ), + '--tab-color': resolveColor( getSyncValue( 'tabColor' ) ), + '--active-title-color': resolveColor( getSyncValue( 'activeTitleColor' ) ), + '--active-title-background': resolveColor( getSyncValue( 'activeTitleBackgroundColor' ) ), + '--active-title-border-color': resolveColor( getSyncValue( 'activeBorderColor' ) ), + '--content-text-color': resolveColor( getSyncValue( 'contentTextColor' ) ), '--title-padding': boxToCSS( getSyncValue( 'titlePadding' ) ), '--content-padding': boxToCSS( getSyncValue( 'contentPadding' ) ), '--border-side-width': 'left' === attributes.tabsPosition ? objectOrNumberAsBox( getSyncValue( 'borderWidth' ) )?.left : objectOrNumberAsBox( getSyncValue( 'borderWidth' ) )?.top, From aaca3d9b13c9dec22bd8cd8590b8fe9fe41ba8cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:45:46 +0000 Subject: [PATCH 32/40] Fix React Hooks dependencies in accordion block Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- src/blocks/blocks/accordion/group/edit.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blocks/blocks/accordion/group/edit.js b/src/blocks/blocks/accordion/group/edit.js index 63e3e186e..eec2791e7 100644 --- a/src/blocks/blocks/accordion/group/edit.js +++ b/src/blocks/blocks/accordion/group/edit.js @@ -8,7 +8,7 @@ import googleFontsLoader from '../../../helpers/google-fonts.js'; * WordPress dependencies. */ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; -import { Fragment, useEffect } from '@wordpress/element'; +import { Fragment, useEffect, useCallback } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -69,7 +69,7 @@ const Edit = ({ } }, []); - const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes }); + const getValue = useCallback( field => getDefaultValueByField({ name, field, defaultAttributes, attributes }), [ name, defaultAttributes, attributes ] ); const resolveColor = useColorResolver(); const inlineStyles = { @@ -131,7 +131,7 @@ const Edit = ({ --title-background: ${ activeTitleBackground }; }` ] : []) ]); - }, [ attributes.activeTitleColor, attributes.activeTitleBackground ]); + }, [ attributes.activeTitleColor, attributes.activeTitleBackground, getValue, resolveColor, setActiveNodeCSS ]); useEffect( () => { if ( attributes.fontFamily ) { From 0c7f0b8bfae4407d079598039857594c47fab215 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:48:22 +0000 Subject: [PATCH 33/40] Remove unnecessary dependency from useCallback in accordion Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- src/blocks/blocks/accordion/group/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blocks/blocks/accordion/group/edit.js b/src/blocks/blocks/accordion/group/edit.js index eec2791e7..d8651532c 100644 --- a/src/blocks/blocks/accordion/group/edit.js +++ b/src/blocks/blocks/accordion/group/edit.js @@ -69,7 +69,7 @@ const Edit = ({ } }, []); - const getValue = useCallback( field => getDefaultValueByField({ name, field, defaultAttributes, attributes }), [ name, defaultAttributes, attributes ] ); + const getValue = useCallback( field => getDefaultValueByField({ name, field, defaultAttributes, attributes }), [ name, attributes ] ); const resolveColor = useColorResolver(); const inlineStyles = { From e008e63b87a7cf4d4e1a22a98b71974a415dea1d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:02:29 +0000 Subject: [PATCH 34/40] Revert useCallback wrapping of getValue to fix accordion initialization Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- src/blocks/blocks/accordion/group/edit.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blocks/blocks/accordion/group/edit.js b/src/blocks/blocks/accordion/group/edit.js index d8651532c..63e3e186e 100644 --- a/src/blocks/blocks/accordion/group/edit.js +++ b/src/blocks/blocks/accordion/group/edit.js @@ -8,7 +8,7 @@ import googleFontsLoader from '../../../helpers/google-fonts.js'; * WordPress dependencies. */ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; -import { Fragment, useEffect, useCallback } from '@wordpress/element'; +import { Fragment, useEffect } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -69,7 +69,7 @@ const Edit = ({ } }, []); - const getValue = useCallback( field => getDefaultValueByField({ name, field, defaultAttributes, attributes }), [ name, attributes ] ); + const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes }); const resolveColor = useColorResolver(); const inlineStyles = { @@ -131,7 +131,7 @@ const Edit = ({ --title-background: ${ activeTitleBackground }; }` ] : []) ]); - }, [ attributes.activeTitleColor, attributes.activeTitleBackground, getValue, resolveColor, setActiveNodeCSS ]); + }, [ attributes.activeTitleColor, attributes.activeTitleBackground ]); useEffect( () => { if ( attributes.fontFamily ) { From d5e0cf54bf779fdcb159271cb96f58bf7175aa40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:57:42 +0000 Subject: [PATCH 35/40] Add test infrastructure for e2e tests (emptytheme, wp-env.json) Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- .wp-env.json | 20 ++++++++++++++++++++ test/emptytheme/functions.php | 13 +++++++++++++ test/emptytheme/index.php | 27 +++++++++++++++++++++++++++ test/emptytheme/style.css | 7 +++++++ 4 files changed, 67 insertions(+) create mode 100644 .wp-env.json create mode 100644 test/emptytheme/functions.php create mode 100644 test/emptytheme/index.php create mode 100644 test/emptytheme/style.css diff --git a/.wp-env.json b/.wp-env.json new file mode 100644 index 000000000..925a064a3 --- /dev/null +++ b/.wp-env.json @@ -0,0 +1,20 @@ +{ + "core": "WordPress/WordPress#6.4", + "plugins": [ + "." + ], + "themes": [ + "./test/emptytheme" + ], + "config": { + "WP_DEBUG": true, + "WP_DEBUG_DISPLAY": true, + "SCRIPT_DEBUG": true + }, + "port": 8889, + "env": { + "tests": { + "port": 8889 + } + } +} diff --git a/test/emptytheme/functions.php b/test/emptytheme/functions.php new file mode 100644 index 000000000..e4a2231db --- /dev/null +++ b/test/emptytheme/functions.php @@ -0,0 +1,13 @@ + + +> + + + + + +> + +
+ +
+ + + diff --git a/test/emptytheme/style.css b/test/emptytheme/style.css new file mode 100644 index 000000000..0c7ee5cab --- /dev/null +++ b/test/emptytheme/style.css @@ -0,0 +1,7 @@ +/* +Theme Name: Empty Theme +Theme URI: https://example.com +Description: A minimal empty theme for testing +Version: 1.0 +Author: Test +*/ From 8d9714db905e6b8339a9e4d599549dd2b9f232a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:58:36 +0000 Subject: [PATCH 36/40] Add comprehensive E2E test setup documentation Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- .wp-env.json | 2 +- E2E_TEST_SETUP.md | 209 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 E2E_TEST_SETUP.md diff --git a/.wp-env.json b/.wp-env.json index 925a064a3..446d76283 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,5 +1,5 @@ { - "core": "WordPress/WordPress#6.4", + "core": null, "plugins": [ "." ], diff --git a/E2E_TEST_SETUP.md b/E2E_TEST_SETUP.md new file mode 100644 index 000000000..90ede2fda --- /dev/null +++ b/E2E_TEST_SETUP.md @@ -0,0 +1,209 @@ +# E2E Test Setup Guide + +This guide explains how to set up and run the end-to-end (e2e) tests for Otter Blocks. + +## Prerequisites + +1. **Docker**: Docker must be installed and running + ```bash + docker --version + docker ps + ``` + +2. **Node.js**: Node.js and npm must be installed + ```bash + node --version + npm --version + ``` + +3. **Network Access**: Internet connection is required to download WordPress and dependencies + +## Setup Steps + +### 1. Install Dependencies + +```bash +npm ci +``` + +This installs all required packages including: +- `@wordpress/env` - WordPress environment manager +- `@playwright/test` - Playwright testing framework +- All other project dependencies + +### 2. Install Playwright Browsers + +```bash +npx playwright install chromium +``` + +This downloads the Chromium browser binary required for running tests. + +### 3. Start WordPress Environment + +```bash +npm run wp-env start +``` + +This command: +- Downloads WordPress (if not cached) +- Creates Docker containers +- Sets up a test WordPress site at http://localhost:8889 +- Installs the Otter Blocks plugin +- Configures test themes and plugins + +**Note**: First run may take 5-10 minutes to download WordPress and set up containers. + +### 4. Build the Plugin + +```bash +npm run build +``` + +Or for development builds: +```bash +npm run build-dev +``` + +### 5. Run E2E Tests + +```bash +npm run test:e2e:playwright +``` + +Additional test commands: +- `npm run test:e2e:playwright-ui` - Run tests with Playwright UI +- `npm run test:performance` - Run performance tests + +## Troubleshooting + +### Network Issues + +If you see errors about network unavailability: +``` +✖ Could not find the current WordPress version in the cache and the network is not available. +``` + +**Solutions:** +1. Ensure you have internet connectivity +2. Check if your firewall allows Docker to access the internet +3. Try clearing the wp-env cache: `npm run wp-env clean all` + +### Docker Issues + +If Docker is not running: +```bash +# On Linux/Mac +sudo service docker start + +# Or check Docker Desktop application +``` + +### Port Conflicts + +If port 8889 is already in use, you can: +1. Stop the conflicting service +2. Change the port in `.wp-env.json` (update both `port` and `env.tests.port`) + +### Reset Environment + +To completely reset the test environment: +```bash +npm run wp-env clean all +npm run wp-env start +``` + +## Test Structure + +- **E2E Tests**: `src/blocks/test/e2e/blocks/` +- **Test Configuration**: `src/blocks/test/e2e/playwright.config.js` +- **Test Theme**: `test/emptytheme/` +- **Test Plugins**: `packages/e2e-tests/plugins/` +- **MU Plugins**: `packages/e2e-tests/mu-plugins/` + +## Configuration Files + +- `.wp-env.json` - Base WordPress environment configuration +- `.wp-env.override.json` - Override configuration with additional settings +- `playwright.config.js` - Playwright test configuration + +## Continuous Integration + +In CI environments (GitHub Actions), the workflow: +1. Installs dependencies +2. Installs Playwright browsers +3. Starts wp-env +4. Builds the plugin +5. Runs e2e tests +6. Uploads test artifacts + +See `.github/workflows/e2e-js.yml` for the complete CI configuration. + +## Development Tips + +### Watch Mode + +For development, you can run tests in watch mode: +```bash +npx playwright test --config src/blocks/test/e2e/playwright.config.js --ui +``` + +### Debug Mode + +To debug failing tests: +```bash +npx playwright test --config src/blocks/test/e2e/playwright.config.js --debug +``` + +### Specific Tests + +To run a specific test file: +```bash +npx playwright test src/blocks/test/e2e/blocks/accordion.spec.js +``` + +### Screenshots and Videos + +Test artifacts (screenshots, videos) are saved to: +- `artifacts/test-results/` - Test results and failure screenshots +- `artifacts/storage-states/` - Authentication state + +## Common Issues + +### Tests Failing After Code Changes + +1. Rebuild the plugin: `npm run build` +2. Restart wp-env: `npm run wp-env restart` +3. Clear browser cache if needed + +### Authentication Issues + +The tests use stored authentication state. If you see login failures: +1. Check `artifacts/storage-states/admin.json` exists +2. Verify the global setup script runs: `src/blocks/test/e2e/global-setup.ts` + +### Timeout Errors + +If tests timeout, you can increase the timeout in `playwright.config.js`: +```javascript +timeout: 100_000, // 100 seconds (default) +``` + +## Getting Help + +- Check test output for specific error messages +- Review test artifacts in `artifacts/` directory +- Check Docker logs: `docker logs ` +- Verify WordPress is running: http://localhost:8889 + +## Cleaning Up + +After testing, to stop and remove containers: +```bash +npm run wp-env stop +``` + +To completely remove all wp-env data: +```bash +npm run wp-env destroy +``` From dda1c64e8d970bc3d29deb34ca3e3ea3e2444698 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:02:45 +0000 Subject: [PATCH 37/40] Revert e2e testing infrastructure additions (already exists in repo) Co-authored-by: selul <3330746+selul@users.noreply.github.com> --- .wp-env.json | 20 ---- E2E_TEST_SETUP.md | 209 ---------------------------------- test/emptytheme/functions.php | 13 --- test/emptytheme/index.php | 27 ----- test/emptytheme/style.css | 7 -- 5 files changed, 276 deletions(-) delete mode 100644 .wp-env.json delete mode 100644 E2E_TEST_SETUP.md delete mode 100644 test/emptytheme/functions.php delete mode 100644 test/emptytheme/index.php delete mode 100644 test/emptytheme/style.css diff --git a/.wp-env.json b/.wp-env.json deleted file mode 100644 index 446d76283..000000000 --- a/.wp-env.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "core": null, - "plugins": [ - "." - ], - "themes": [ - "./test/emptytheme" - ], - "config": { - "WP_DEBUG": true, - "WP_DEBUG_DISPLAY": true, - "SCRIPT_DEBUG": true - }, - "port": 8889, - "env": { - "tests": { - "port": 8889 - } - } -} diff --git a/E2E_TEST_SETUP.md b/E2E_TEST_SETUP.md deleted file mode 100644 index 90ede2fda..000000000 --- a/E2E_TEST_SETUP.md +++ /dev/null @@ -1,209 +0,0 @@ -# E2E Test Setup Guide - -This guide explains how to set up and run the end-to-end (e2e) tests for Otter Blocks. - -## Prerequisites - -1. **Docker**: Docker must be installed and running - ```bash - docker --version - docker ps - ``` - -2. **Node.js**: Node.js and npm must be installed - ```bash - node --version - npm --version - ``` - -3. **Network Access**: Internet connection is required to download WordPress and dependencies - -## Setup Steps - -### 1. Install Dependencies - -```bash -npm ci -``` - -This installs all required packages including: -- `@wordpress/env` - WordPress environment manager -- `@playwright/test` - Playwright testing framework -- All other project dependencies - -### 2. Install Playwright Browsers - -```bash -npx playwright install chromium -``` - -This downloads the Chromium browser binary required for running tests. - -### 3. Start WordPress Environment - -```bash -npm run wp-env start -``` - -This command: -- Downloads WordPress (if not cached) -- Creates Docker containers -- Sets up a test WordPress site at http://localhost:8889 -- Installs the Otter Blocks plugin -- Configures test themes and plugins - -**Note**: First run may take 5-10 minutes to download WordPress and set up containers. - -### 4. Build the Plugin - -```bash -npm run build -``` - -Or for development builds: -```bash -npm run build-dev -``` - -### 5. Run E2E Tests - -```bash -npm run test:e2e:playwright -``` - -Additional test commands: -- `npm run test:e2e:playwright-ui` - Run tests with Playwright UI -- `npm run test:performance` - Run performance tests - -## Troubleshooting - -### Network Issues - -If you see errors about network unavailability: -``` -✖ Could not find the current WordPress version in the cache and the network is not available. -``` - -**Solutions:** -1. Ensure you have internet connectivity -2. Check if your firewall allows Docker to access the internet -3. Try clearing the wp-env cache: `npm run wp-env clean all` - -### Docker Issues - -If Docker is not running: -```bash -# On Linux/Mac -sudo service docker start - -# Or check Docker Desktop application -``` - -### Port Conflicts - -If port 8889 is already in use, you can: -1. Stop the conflicting service -2. Change the port in `.wp-env.json` (update both `port` and `env.tests.port`) - -### Reset Environment - -To completely reset the test environment: -```bash -npm run wp-env clean all -npm run wp-env start -``` - -## Test Structure - -- **E2E Tests**: `src/blocks/test/e2e/blocks/` -- **Test Configuration**: `src/blocks/test/e2e/playwright.config.js` -- **Test Theme**: `test/emptytheme/` -- **Test Plugins**: `packages/e2e-tests/plugins/` -- **MU Plugins**: `packages/e2e-tests/mu-plugins/` - -## Configuration Files - -- `.wp-env.json` - Base WordPress environment configuration -- `.wp-env.override.json` - Override configuration with additional settings -- `playwright.config.js` - Playwright test configuration - -## Continuous Integration - -In CI environments (GitHub Actions), the workflow: -1. Installs dependencies -2. Installs Playwright browsers -3. Starts wp-env -4. Builds the plugin -5. Runs e2e tests -6. Uploads test artifacts - -See `.github/workflows/e2e-js.yml` for the complete CI configuration. - -## Development Tips - -### Watch Mode - -For development, you can run tests in watch mode: -```bash -npx playwright test --config src/blocks/test/e2e/playwright.config.js --ui -``` - -### Debug Mode - -To debug failing tests: -```bash -npx playwright test --config src/blocks/test/e2e/playwright.config.js --debug -``` - -### Specific Tests - -To run a specific test file: -```bash -npx playwright test src/blocks/test/e2e/blocks/accordion.spec.js -``` - -### Screenshots and Videos - -Test artifacts (screenshots, videos) are saved to: -- `artifacts/test-results/` - Test results and failure screenshots -- `artifacts/storage-states/` - Authentication state - -## Common Issues - -### Tests Failing After Code Changes - -1. Rebuild the plugin: `npm run build` -2. Restart wp-env: `npm run wp-env restart` -3. Clear browser cache if needed - -### Authentication Issues - -The tests use stored authentication state. If you see login failures: -1. Check `artifacts/storage-states/admin.json` exists -2. Verify the global setup script runs: `src/blocks/test/e2e/global-setup.ts` - -### Timeout Errors - -If tests timeout, you can increase the timeout in `playwright.config.js`: -```javascript -timeout: 100_000, // 100 seconds (default) -``` - -## Getting Help - -- Check test output for specific error messages -- Review test artifacts in `artifacts/` directory -- Check Docker logs: `docker logs ` -- Verify WordPress is running: http://localhost:8889 - -## Cleaning Up - -After testing, to stop and remove containers: -```bash -npm run wp-env stop -``` - -To completely remove all wp-env data: -```bash -npm run wp-env destroy -``` diff --git a/test/emptytheme/functions.php b/test/emptytheme/functions.php deleted file mode 100644 index e4a2231db..000000000 --- a/test/emptytheme/functions.php +++ /dev/null @@ -1,13 +0,0 @@ - - -> - - - - - -> - -
- -
- - - diff --git a/test/emptytheme/style.css b/test/emptytheme/style.css deleted file mode 100644 index 0c7ee5cab..000000000 --- a/test/emptytheme/style.css +++ /dev/null @@ -1,7 +0,0 @@ -/* -Theme Name: Empty Theme -Theme URI: https://example.com -Description: A minimal empty theme for testing -Version: 1.0 -Author: Test -*/ From 0cceac46efae7d2af430aa272bad72725be072c1 Mon Sep 17 00:00:00 2001 From: Soare Robert Daniel Date: Wed, 1 Apr 2026 14:01:02 +0300 Subject: [PATCH 38/40] refactor: update black friday labels (#2755) --- inc/class-main.php | 54 +++++++++++++------ plugins/blocks-animation/blocks-animation.php | 13 +++-- plugins/blocks-css/blocks-css.php | 13 +++-- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/inc/class-main.php b/inc/class-main.php index ae922ee80..c7a855b77 100644 --- a/inc/class-main.php +++ b/inc/class-main.php @@ -599,30 +599,52 @@ public function about_page() { public function add_black_friday_data( $configs ) { $config = $configs['default']; - // translators: %1$s - HTML tag, %2$s - discount, %3$s - HTML tag, %4$s - product name. - $message_template = __( 'Our biggest sale of the year: %1$sup to %2$s OFF%3$s on %4$s. Don\'t miss this limited-time offer.', 'otter-blocks' ); - $product_label = 'Otter Blocks'; - $discount = '70%'; + $message = __( 'Advanced blocks, custom CSS, WooCommerce integration. Everything you need to build better pages, without code. Exclusively for existing Otter users.', 'otter-blocks' ); + $cta_label = __( 'Get Otter Pro', 'otter-blocks' ); $plan = apply_filters( 'product_otter_license_plan', 0 ); $license = apply_filters( 'product_otter_license_key', false ); - $is_pro = 0 < $plan; + $status = apply_filters( 'product_otter_license_status', false ); + + $is_pro = 'valid' === $status; + $is_expired = 'expired' === $status || 'active-expired' === $status; + $pro_product_slug = defined( 'OTTER_PRO_BASEFILE' ) ? basename( dirname( OTTER_PRO_BASEFILE ) ) : ''; if ( $is_pro ) { - // translators: %1$s - HTML tag, %2$s - discount, %3$s - HTML tag, %4$s - product name. - $message_template = __( 'Get %1$sup to %2$s off%3$s when you upgrade your %4$s plan or renew early.', 'otter-blocks' ); - $product_label = 'Otter Pro'; - $discount = '30%'; - } - - $product_label = sprintf( '%s', $product_label ); - $url_params = array( + // translators: %s is the discount percentage. + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - up to %s off', 'otter-blocks' ), '30%' ); + // translators: %1$s - discount, %2$s - discount. + $message = sprintf( __( 'Upgrade your Otter Pro plan: %1$s off this week. Already on the plan you need? Renew early and save up to %2$s.', 'otter-blocks' ), '30%', '20%' ); + $cta_label = __( 'See your options', 'otter-blocks' ); + } elseif ( $is_expired ) { + // translators: %s is the discount percentage. + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - %s off', 'otter-blocks' ), '50%' ); + // translators: %s is the discount percentage. + $config['upgrade_menu_text'] = sprintf( __( 'BF Sale - %s off', 'otter-blocks' ), '50%' ); + $message = __( 'Your Otter Pro features are still here, just locked. Renew at a reduced rate this week.', 'otter-blocks' ); + $cta_label = __( 'Reactivate now', 'otter-blocks' ); + } else { + // translators: %s is the discount percentage. + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - %s off', 'otter-blocks' ), '60%' ); + // translators: %s is the discount percentage. + $config['upgrade_menu_text'] = sprintf( __( 'BF Sale - %s off', 'otter-blocks' ), '60%' ); + // translators: %s - discount. + $config['title'] = sprintf( __( 'Otter Pro: %s off this week', 'otter-blocks' ), '60%' ); + } + + $url_params = array( 'utm_term' => $is_pro ? 'plan-' . $plan : 'free', 'lkey' => ! empty( $license ) ? $license : false, + 'expired' => $is_expired ? '1' : false, ); - - $config['message'] = sprintf( $message_template, '', $discount, '', $product_label ); - $config['sale_url'] = add_query_arg( + + if ( ( $is_pro || $is_expired ) && ! empty( $pro_product_slug ) ) { + $config['plugin_meta_targets'] = array( $pro_product_slug ); + } + + $config['cta_label'] = $cta_label; + $config['message'] = $message; + $config['sale_url'] = add_query_arg( $url_params, tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/otter-bf', 'bfcm', 'otter' ) ) ); diff --git a/plugins/blocks-animation/blocks-animation.php b/plugins/blocks-animation/blocks-animation.php index 1f44cddf8..035abd2f0 100644 --- a/plugins/blocks-animation/blocks-animation.php +++ b/plugins/blocks-animation/blocks-animation.php @@ -72,19 +72,18 @@ function ( $configs ) { $config = $configs['default']; - // translators: %1$s - plugin name, %2$s - plugin name, %3$s - discount. - $message_template = __( 'Extend %1$s with %2$s – up to %3$s OFF in our biggest sale of the year. Limited time only.', 'blocks-animation' ); - - $config['message'] = sprintf( $message_template, 'Block Animation', 'Otter Pro Blocks', '70%' ); - $config['sale_url'] = add_query_arg( + // translators: 1. Number of free licenses, 2. The price of the product. + $config['message'] = sprintf( __( 'You’re using Blocks Animation, and the team behind it is celebrating Black Friday by giving away %1$s licences of Otter Pro. A powerful block collection worth %2$s, with advanced blocks, custom CSS, animations, and WooCommerce integration. Claim yours before they run out.', 'blocks-animation' ), 100, '$69' ); + $config['plugin_meta_message'] = __( 'Black Friday Sale - Get Otter Pro free', 'blocks-animation' ); + $config['sale_url'] = add_query_arg( array( 'utm_term' => 'free', ), - tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/otter-bf', 'bfcm', 'blocks-animation' ) ) + tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/otter-claim-bf', 'bfcm', 'blocks-animation' ) ) ); $configs[ BLOCKS_ANIMATION_PRODUCT_SLUG ] = $config; return $configs; - } + } ); diff --git a/plugins/blocks-css/blocks-css.php b/plugins/blocks-css/blocks-css.php index 0531745f8..1a18f1fea 100644 --- a/plugins/blocks-css/blocks-css.php +++ b/plugins/blocks-css/blocks-css.php @@ -68,19 +68,18 @@ function ( $configs ) { $config = $configs['default']; - // translators: %1$s - plugin name, %2$s - plugin name, %3$s - discount. - $message_template = __( 'Extend %1$s with %2$s – up to %3$s OFF in our biggest sale of the year. Limited time only.', 'blocks-css' ); - - $config['message'] = sprintf( $message_template, 'Blocks CSS', 'Otter Pro Blocks', '70%' ); - $config['sale_url'] = add_query_arg( + // translators: 1. Number of free licenses, 2. The price of the product. + $config['message'] = sprintf( __( 'You’re using Blocks CSS, and the team behind it is celebrating Black Friday by giving away %1$s licences of Otter Pro. A powerful block collection worth %2$s, with advanced blocks, custom CSS, animations, and WooCommerce integration. Claim yours before they run out.', 'blocks-css' ), 100, '$69' ); + $config['plugin_meta_message'] = __( 'Black Friday Sale - Get Otter Pro free', 'blocks-css' ); + $config['sale_url'] = add_query_arg( array( 'utm_term' => 'free', ), - tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/otter-bf', 'bfcm', 'blocks-css' ) ) + tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/otter-claim-bf', 'bfcm', 'blocks-css' ) ) ); $configs[ BLOCKS_CSS_PRODUCT_SLUG ] = $config; return $configs; - } + } ); From 3290d4134d9f73c15dc24c9827bf6eac70d0d8dc Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 1 Apr 2026 16:35:22 +0530 Subject: [PATCH 39/40] fix: accordion icon on large title --- src/blocks/blocks/accordion/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/blocks/blocks/accordion/style.scss b/src/blocks/blocks/accordion/style.scss index 6ce2c10a3..41bd9cf39 100644 --- a/src/blocks/blocks/accordion/style.scss +++ b/src/blocks/blocks/accordion/style.scss @@ -166,6 +166,7 @@ &:not( .has-icon ) > &-item:not([open]) > &-item__title::after { transform: rotate(45deg) translate(-25%, 0%); + flex-shrink: 0; } &:not( .has-open-icon ) > &-item[open] > &-item__title::after { From b1464abd6d0e26607e88b02670c51b344f9ef820 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Wed, 1 Apr 2026 17:15:15 +0530 Subject: [PATCH 40/40] fix: accordion icon on large title --- src/blocks/blocks/accordion/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blocks/blocks/accordion/style.scss b/src/blocks/blocks/accordion/style.scss index 41bd9cf39..28c2d2cfe 100644 --- a/src/blocks/blocks/accordion/style.scss +++ b/src/blocks/blocks/accordion/style.scss @@ -162,11 +162,11 @@ border-bottom: 2px solid currentColor; width: 8px; height: 8px; + flex-shrink: 0; } &:not( .has-icon ) > &-item:not([open]) > &-item__title::after { transform: rotate(45deg) translate(-25%, 0%); - flex-shrink: 0; } &:not( .has-open-icon ) > &-item[open] > &-item__title::after {