From 108f91952dfc7abfb9c4e3b890602db6d5e4557d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andres.maneiro@automattic.com>
Date: Mon, 10 May 2021 18:30:05 +0200
Subject: [PATCH 01/10] Modify the merge algorithm so all the colors are kept

---
 lib/class-wp-theme-json.php | 43 ++++++++++++++++++++++++++-----------
 1 file changed, 30 insertions(+), 13 deletions(-)

diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php
index 6734d439b10f9b..18af192f9a4846 100644
--- a/lib/class-wp-theme-json.php
+++ b/lib/class-wp-theme-json.php
@@ -1079,30 +1079,47 @@ public function get_stylesheet( $type = 'all' ) {
 	 */
 	public function merge( $incoming ) {
 		$incoming_data    = $incoming->get_raw_data();
-		$this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data );
+		$existing_data    = $this->theme_json;
 
 		// The array_replace_recursive algorithm merges at the leaf level.
 		// For leaf values that are arrays it will use the numeric indexes for replacement.
-		// In those cases, what we want is to use the incoming value, if it exists.
-		//
-		// These are the cases that have array values at the leaf levels.
-		$properties   = array();
-		$properties[] = array( 'color', 'palette' );
-		$properties[] = array( 'color', 'gradients' );
-		$properties[] = array( 'custom' );
-		$properties[] = array( 'spacing', 'units' );
-		$properties[] = array( 'typography', 'fontSizes' );
-		$properties[] = array( 'typography', 'fontFamilies' );
+		$this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data );
+
+		// There are a few cases in which we want to merge things differently
+		// from what array_replace_recursive does.
+
+		// Some incoming properties should replace the existing.
+		$to_replace   = array();
+		$to_replace[] = array( 'custom' );
+		$to_replace[] = array( 'spacing', 'units' );
+		$to_replace[] = array( 'typography', 'fontSizes' );
+		$to_replace[] = array( 'typography', 'fontFamilies' );
+
+		// Some others should be appended to the existing.
+		$to_append   = array();
+		$to_append[] = array( 'color', 'palette' );
+		$to_append[] = array( 'color', 'gradients' );
 
 		$nodes = self::get_setting_nodes( $this->theme_json );
 		foreach ( $nodes as $metadata ) {
-			foreach ( $properties as $property_path ) {
-				$path = array_merge( $metadata['path'], $property_path );
+			foreach ( $to_replace as $path_to_replace ) {
+				$path = array_merge( $metadata['path'], $path_to_replace );
 				$node = _wp_array_get( $incoming_data, $path, array() );
 				if ( ! empty( $node ) ) {
 					gutenberg_experimental_set( $this->theme_json, $path, $node );
 				}
 			}
+			foreach ( $to_append as $path_to_append ) {
+				$path          = array_merge( $metadata['path'], $path_to_append );
+				$incoming_node = _wp_array_get( $incoming_data, $path, array() );
+				$existing_node = _wp_array_get( $existing_data, $path, array() );
+
+				if ( empty( $incoming_node ) && empty( $existing_data ) ) {
+					continue;
+				}
+
+				gutenberg_experimental_set( $this->theme_json, $path, array_merge( $existing_node, $incoming_node ) );
+			}
 		}
 
 	}

From e0b6d6ce73209a3467eb4ea2c03fbc1f7d3be175 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andres.maneiro@automattic.com>
Date: Fri, 14 May 2021 17:34:02 +0200
Subject: [PATCH 02/10] Do not show core colors in the UI controls

---
 lib/experimental-default-theme.json           | 72 ++++++++++++-------
 .../src/components/use-setting/index.js       | 22 +++++-
 .../edit-site/src/components/editor/utils.js  | 15 +++-
 3 files changed, 82 insertions(+), 27 deletions(-)

diff --git a/lib/experimental-default-theme.json b/lib/experimental-default-theme.json
index d92547a1468952..dc4dbbaf058529 100644
--- a/lib/experimental-default-theme.json
+++ b/lib/experimental-default-theme.json
@@ -6,124 +6,148 @@
 				{
 					"name": "Black",
 					"slug": "black",
-					"color": "#000000"
+					"color": "#000000",
+					"origin": "core"
 				},
 				{
 					"name": "Cyan bluish gray",
 					"slug": "cyan-bluish-gray",
-					"color": "#abb8c3"
+					"color": "#abb8c3",
+					"origin": "core"
 				},
 				{
 					"name": "White",
 					"slug": "white",
-					"color": "#ffffff"
+					"color": "#ffffff",
+					"origin": "core"
 				},
 				{
 					"name": "Pale pink",
 					"slug": "pale-pink",
-					"color": "#f78da7"
+					"color": "#f78da7",
+					"origin": "core"
 				},
 				{
 					"name": "Vivid red",
 					"slug": "vivid-red",
-					"color": "#cf2e2e"
+					"color": "#cf2e2e",
+					"origin": "core"
 				},
 				{
 					"name": "Luminous vivid orange",
 					"slug": "luminous-vivid-orange",
-					"color": "#ff6900"
+					"color": "#ff6900",
+					"origin": "core"
 				},
 				{
 					"name": "Luminous vivid amber",
 					"slug": "luminous-vivid-amber",
-					"color": "#fcb900"
+					"color": "#fcb900",
+					"origin": "core"
 				},
 				{
 					"name": "Light green cyan",
 					"slug": "light-green-cyan",
-					"color": "#7bdcb5"
+					"color": "#7bdcb5",
+					"origin": "core"
 				},
 				{
 					"name": "Vivid green cyan",
 					"slug": "vivid-green-cyan",
-					"color": "#00d084"
+					"color": "#00d084",
+					"origin": "core"
 				},
 				{
 					"name": "Pale cyan blue",
 					"slug": "pale-cyan-blue",
-					"color": "#8ed1fc"
+					"color": "#8ed1fc",
+					"origin": "core"
 				},
 				{
 					"name": "Vivid cyan blue",
 					"slug": "vivid-cyan-blue",
-					"color": "#0693e3"
+					"color": "#0693e3",
+					"origin": "core"
 				},
 				{
 					"name": "Vivid purple",
 					"slug": "vivid-purple",
-					"color": "#9b51e0"
+					"color": "#9b51e0",
+					"origin": "core"
 				}
 			],
 			"gradients": [
 				{
 					"name": "Vivid cyan blue to vivid purple",
 					"gradient": "linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)",
-					"slug": "vivid-cyan-blue-to-vivid-purple"
+					"slug": "vivid-cyan-blue-to-vivid-purple",
+					"origin": "core"
 				},
 				{
 					"name": "Light green cyan to vivid green cyan",
 					"gradient": "linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)",
-					"slug": "light-green-cyan-to-vivid-green-cyan"
+					"slug": "light-green-cyan-to-vivid-green-cyan",
+					"origin": "core"
 				},
 				{
 					"name": "Luminous vivid amber to luminous vivid orange",
 					"gradient": "linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)",
-					"slug": "luminous-vivid-amber-to-luminous-vivid-orange"
+					"slug": "luminous-vivid-amber-to-luminous-vivid-orange",
+					"origin": "core"
 				},
 				{
 					"name": "Luminous vivid orange to vivid red",
 					"gradient": "linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)",
-					"slug": "luminous-vivid-orange-to-vivid-red"
+					"slug": "luminous-vivid-orange-to-vivid-red",
+					"origin": "core"
 				},
 				{
 					"name": "Very light gray to cyan bluish gray",
 					"gradient": "linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)",
-					"slug": "very-light-gray-to-cyan-bluish-gray"
+					"slug": "very-light-gray-to-cyan-bluish-gray",
+					"origin": "core"
 				},
 				{
 					"name": "Cool to warm spectrum",
 					"gradient": "linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)",
-					"slug": "cool-to-warm-spectrum"
+					"slug": "cool-to-warm-spectrum",
+					"origin": "core"
 				},
 				{
 					"name": "Blush light purple",
 					"gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)",
-					"slug": "blush-light-purple"
+					"slug": "blush-light-purple",
+					"origin": "core"
 				},
 				{
 					"name": "Blush bordeaux",
 					"gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)",
-					"slug": "blush-bordeaux"
+					"slug": "blush-bordeaux",
+					"origin": "core"
 				},
 				{
 					"name": "Luminous dusk",
 					"gradient": "linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)",
-					"slug": "luminous-dusk"
+					"slug": "luminous-dusk",
+					"origin": "core"
 				},
 				{
 					"name": "Pale ocean",
 					"gradient": "linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)",
-					"slug": "pale-ocean"
+					"slug": "pale-ocean",
+					"origin": "core"
 				},
 				{
 					"name": "Electric grass",
 					"gradient": "linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)",
-					"slug": "electric-grass"
+					"slug": "electric-grass",
+					"origin": "core"
 				},
 				{
 					"name": "Midnight",
 					"gradient": "linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)",
-					"slug": "midnight"
+					"slug": "midnight",
+					"origin": "core"
 				}
 			],
 			"duotone": [
diff --git a/packages/block-editor/src/components/use-setting/index.js b/packages/block-editor/src/components/use-setting/index.js
index ec0e813eb8eace..ce8c6d0da519d8 100644
--- a/packages/block-editor/src/components/use-setting/index.js
+++ b/packages/block-editor/src/components/use-setting/index.js
@@ -49,6 +49,18 @@ const deprecatedFlags = {
 	'spacing.customPadding': ( settings ) => settings.enableCustomSpacing,
 };
 
+const filterColorsFromCoreOrigin = ( path, colors ) => {
+	if ( path !== 'color.palette' && path !== 'color.gradients' ) {
+		return colors;
+	}
+
+	if ( ! Array.isArray( colors ) ) {
+		return colors;
+	}
+
+	return colors.filter( ( color ) => color?.origin !== 'core' );
+};
+
 /**
  * Hook that retrieves the editor setting.
  * It works with nested objects using by finding the value at path.
@@ -76,7 +88,10 @@ export default function useSetting( path ) {
 			const experimentalFeaturesResult =
 				get( settings, blockPath ) ?? get( settings, defaultsPath );
 			if ( experimentalFeaturesResult !== undefined ) {
-				return experimentalFeaturesResult;
+				return filterColorsFromCoreOrigin(
+					path,
+					experimentalFeaturesResult
+				);
 			}
 
 			// 2 - Use deprecated settings, otherwise.
@@ -84,7 +99,10 @@ export default function useSetting( path ) {
 				? deprecatedFlags[ path ]( settings )
 				: undefined;
 			if ( deprecatedSettingsValue !== undefined ) {
-				return deprecatedSettingsValue;
+				return filterColorsFromCoreOrigin(
+					path,
+					deprecatedSettingsValue
+				);
 			}
 
 			// 3 - Fall back for typography.dropCap:
diff --git a/packages/edit-site/src/components/editor/utils.js b/packages/edit-site/src/components/editor/utils.js
index 865279b4625ba4..9fa9c7274628ca 100644
--- a/packages/edit-site/src/components/editor/utils.js
+++ b/packages/edit-site/src/components/editor/utils.js
@@ -90,13 +90,26 @@ function getPresetMetadataFromStyleProperty( styleProperty ) {
 	return getPresetMetadataFromStyleProperty.MAP[ styleProperty ];
 }
 
+const filterColorsFromCoreOrigin = ( path, colors ) => {
+	if ( path !== 'color.palette' && path !== 'color.gradients' ) {
+		return colors;
+	}
+
+	if ( ! Array.isArray( colors ) ) {
+		return colors;
+	}
+
+	return colors.filter( ( color ) => color?.origin !== 'core' );
+};
+
 export function useSetting( path, blockName = '' ) {
 	const settings = useSelect( ( select ) => {
 		return select( editSiteStore ).getSettings();
 	} );
 	const topLevelPath = `__experimentalFeatures.${ path }`;
 	const blockPath = `__experimentalFeatures.blocks.${ blockName }.${ path }`;
-	return get( settings, blockPath ) ?? get( settings, topLevelPath );
+	const setting = get( settings, blockPath ) ?? get( settings, topLevelPath );
+	return filterColorsFromCoreOrigin( path, setting );
 }
 
 export function getPresetVariable( styles, context, propertyName, value ) {

From 4ddd1defeee4f46ccc23f4a48d0907c491b8169a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andres.maneiro@automattic.com>
Date: Tue, 18 May 2021 11:33:45 +0200
Subject: [PATCH 03/10] Color palette and gradients are merged

---
 phpunit/class-wp-theme-json-test.php | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php
index 5b6d02059829f2..14851a00f4dbf8 100644
--- a/phpunit/class-wp-theme-json-test.php
+++ b/phpunit/class-wp-theme-json-test.php
@@ -410,6 +410,14 @@ public function test_merge_incoming_data() {
 					'custom'         => true,
 					'customGradient' => true,
 					'palette'        => array(
+						array(
+							'slug'  => 'red',
+							'color' => 'red',
+						),
+						array(
+							'slug'  => 'green',
+							'color' => 'green',
+						),
 						array(
 							'slug'  => 'blue',
 							'color' => 'blue',

From b7fbedf500791d2d44eec9d93892dd86b2d22642 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andres.maneiro@automattic.com>
Date: Tue, 18 May 2021 11:35:41 +0200
Subject: [PATCH 04/10] Do not add palette and gradients if empty

---
 lib/class-wp-theme-json.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php
index 18af192f9a4846..0160fada5810d2 100644
--- a/lib/class-wp-theme-json.php
+++ b/lib/class-wp-theme-json.php
@@ -1114,7 +1114,7 @@ public function merge( $incoming ) {
 				$incoming_node = _wp_array_get( $incoming_data, $path, array() );
 				$existing_node = _wp_array_get( $existing_data, $path, array() );
 
-				if ( empty( $incoming_node ) && empty( $existing_data ) ) {
+				if ( empty( $incoming_node ) && empty( $existing_node ) ) {
 					continue;
 				}
 

From 3aa6568bc247ed1a3240937c52959e2aa95ffb03 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andres.maneiro@automattic.com>
Date: Tue, 18 May 2021 16:42:46 +0200
Subject: [PATCH 05/10] Fix linting issues

---
 lib/class-wp-theme-json.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php
index 0160fada5810d2..f42efec8fb727f 100644
--- a/lib/class-wp-theme-json.php
+++ b/lib/class-wp-theme-json.php
@@ -1078,8 +1078,8 @@ public function get_stylesheet( $type = 'all' ) {
 	 * @param WP_Theme_JSON $incoming Data to merge.
 	 */
 	public function merge( $incoming ) {
-		$incoming_data    = $incoming->get_raw_data();
-		$existing_data    = $this->theme_json;
+		$incoming_data = $incoming->get_raw_data();
+		$existing_data = $this->theme_json;
 
 		// The array_replace_recursive algorithm merges at the leaf level.
 		// For leaf values that are arrays it will use the numeric indexes for replacement.

From ee4ffe7da735edcd92a983febccb9ae01716ee6d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andres.maneiro@automattic.com>
Date: Wed, 19 May 2021 10:18:41 +0200
Subject: [PATCH 06/10] Cover the case in which the theme does not provide any
 color

---
 .../block-editor/src/components/use-setting/index.js | 12 +++++++-----
 packages/edit-site/src/components/editor/utils.js    | 12 +++++++-----
 2 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/packages/block-editor/src/components/use-setting/index.js b/packages/block-editor/src/components/use-setting/index.js
index ce8c6d0da519d8..43ee185c396617 100644
--- a/packages/block-editor/src/components/use-setting/index.js
+++ b/packages/block-editor/src/components/use-setting/index.js
@@ -49,16 +49,18 @@ const deprecatedFlags = {
 	'spacing.customPadding': ( settings ) => settings.enableCustomSpacing,
 };
 
-const filterColorsFromCoreOrigin = ( path, colors ) => {
+const filterColorsFromCoreOrigin = ( path, setting ) => {
 	if ( path !== 'color.palette' && path !== 'color.gradients' ) {
-		return colors;
+		return setting;
 	}
 
-	if ( ! Array.isArray( colors ) ) {
-		return colors;
+	if ( ! Array.isArray( setting ) ) {
+		return setting;
 	}
 
-	return colors.filter( ( color ) => color?.origin !== 'core' );
+	const colors = setting.filter( ( color ) => color?.origin !== 'core' );
+
+	return colors.length > 0 ? colors : setting;
 };
 
 /**
diff --git a/packages/edit-site/src/components/editor/utils.js b/packages/edit-site/src/components/editor/utils.js
index 9fa9c7274628ca..7d5d55884feeeb 100644
--- a/packages/edit-site/src/components/editor/utils.js
+++ b/packages/edit-site/src/components/editor/utils.js
@@ -90,16 +90,18 @@ function getPresetMetadataFromStyleProperty( styleProperty ) {
 	return getPresetMetadataFromStyleProperty.MAP[ styleProperty ];
 }
 
-const filterColorsFromCoreOrigin = ( path, colors ) => {
+const filterColorsFromCoreOrigin = ( path, setting ) => {
 	if ( path !== 'color.palette' && path !== 'color.gradients' ) {
-		return colors;
+		return setting;
 	}
 
-	if ( ! Array.isArray( colors ) ) {
-		return colors;
+	if ( ! Array.isArray( setting ) ) {
+		return setting;
 	}
 
-	return colors.filter( ( color ) => color?.origin !== 'core' );
+	const colors = setting.filter( ( color ) => color?.origin !== 'core' );
+
+	return colors.length > 0 ? colors : setting;
 };
 
 export function useSetting( path, blockName = '' ) {

From 9faec4d42edcb6e739dc8a95679212b4b02384ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andres.maneiro@automattic.com>
Date: Wed, 19 May 2021 20:07:10 +0200
Subject: [PATCH 07/10] Update merge algorithm for user colors

---
 .../editor/global-styles-provider.js          | 26 ++++++++++++++++++-
 .../components/sidebar/color-palette-panel.js | 18 +++++++++++--
 2 files changed, 41 insertions(+), 3 deletions(-)

diff --git a/packages/edit-site/src/components/editor/global-styles-provider.js b/packages/edit-site/src/components/editor/global-styles-provider.js
index 7fdf78b060a829..6d173773a18499 100644
--- a/packages/edit-site/src/components/editor/global-styles-provider.js
+++ b/packages/edit-site/src/components/editor/global-styles-provider.js
@@ -47,7 +47,31 @@ const GlobalStylesContext = createContext( {
 	/* eslint-enable no-unused-vars */
 } );
 
-const mergeTreesCustomizer = ( objValue, srcValue ) => {
+const mergeTreesCustomizer = ( objValue, srcValue, key ) => {
+	// Users can add their own colors.
+	// We want to append them when they don't
+	// have the same slug as an existing color,
+	// otherwise we want to update the existing color instead.
+	if ( 'palette' === key ) {
+		const indexTable = {};
+		const existingSlugs = [];
+		const result = [ ...( objValue ?? [] ) ];
+		result.forEach( ( { slug }, index ) => {
+			indexTable[ slug ] = index;
+			existingSlugs.push( slug );
+		} );
+
+		( srcValue ?? [] ).forEach( ( element ) => {
+			if ( existingSlugs.includes( element?.slug ) ) {
+				result[ indexTable[ element?.slug ] ] = element;
+			} else {
+				result.push( element );
+			}
+		} );
+
+		return result;
+	}
+
 	// We only pass as arrays the presets,
 	// in which case we want the new array of values
 	// to override the old array (no merging).
diff --git a/packages/edit-site/src/components/sidebar/color-palette-panel.js b/packages/edit-site/src/components/sidebar/color-palette-panel.js
index e3fbda55711974..49e7c964fc29c8 100644
--- a/packages/edit-site/src/components/sidebar/color-palette-panel.js
+++ b/packages/edit-site/src/components/sidebar/color-palette-panel.js
@@ -1,7 +1,7 @@
 /**
  * External dependencies
  */
-import { get } from 'lodash';
+import { difference, get } from 'lodash';
 
 /**
  * WordPress dependencies
@@ -58,7 +58,21 @@ export default function ColorPalettePanel( {
 			immutableColorSlugs={ immutableColorSlugs }
 			colors={ colors }
 			onChange={ ( newColors ) => {
-				setSetting( contextName, 'color.palette', newColors );
+				const existingUserColors = ( newColors ?? [] ).filter(
+					( color ) => color.origin === 'user'
+				);
+				const differentUserColors = difference( newColors, colors );
+				if ( differentUserColors.length === 1 ) {
+					differentUserColors[ 0 ] = {
+						...differentUserColors[ 0 ],
+						origin: 'user',
+					};
+				}
+
+				setSetting( contextName, 'color.palette', [
+					...existingUserColors,
+					...differentUserColors,
+				] );
 			} }
 			emptyUI={ __(
 				'Colors are empty! Add some colors to create your own color palette.'

From 0c4653fc8ae1342a3846afcbaf714da4a357beb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andres.maneiro@automattic.com>
Date: Wed, 19 May 2021 20:45:56 +0200
Subject: [PATCH 08/10] Update merge algorithm for colors in the server

---
 lib/class-wp-theme-json.php | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php
index f42efec8fb727f..eb723a7f0cf60b 100644
--- a/lib/class-wp-theme-json.php
+++ b/lib/class-wp-theme-json.php
@@ -1095,7 +1095,8 @@ public function merge( $incoming ) {
 		$to_replace[] = array( 'typography', 'fontSizes' );
 		$to_replace[] = array( 'typography', 'fontFamilies' );
 
-		// Some others should be appended to the existing.
+		// Some others should be either appended to the existing
+		// or update the existing if the slug is the same.
 		$to_append   = array();
 		$to_append[] = array( 'color', 'palette' );
 		$to_append[] = array( 'color', 'gradients' );
@@ -1118,7 +1119,24 @@ public function merge( $incoming ) {
 					continue;
 				}
 
-				gutenberg_experimental_set( $this->theme_json, $path, array_merge( $existing_node, $incoming_node ) );
+				$index_table    = array();
+				$existing_slugs = array();
+				$merged         = array();
+				foreach( $existing_node as $key => $value ) {
+					$index_table[ $value['slug'] ] = $key;
+					$existing_slugs[]              = $value['slug'];
+					$merged[ $key ]                = $value;
+				}
+
+				foreach( $incoming_node as $value ) {
+					if ( in_array( $value['slug'], $existing_slugs ) ) {
+						$merged[ $index_table[ $value['slug'] ] ] = $value;
+					} else {
+						$merged[] = $value;
+					}
+				}
+
+				gutenberg_experimental_set( $this->theme_json, $path, $merged );
 			}
 		}
 

From c9fc39f4b01bd072828888224b1351db056125c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andres.maneiro@automattic.com>
Date: Thu, 20 May 2021 09:35:30 +0200
Subject: [PATCH 09/10] Fix color ordering

---
 lib/class-wp-theme-json-resolver.php |  2 +-
 lib/class-wp-theme-json.php          | 29 ++++++++++++++++++++++------
 2 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/lib/class-wp-theme-json-resolver.php b/lib/class-wp-theme-json-resolver.php
index 4df3881a72c565..96e04f43c80216 100644
--- a/lib/class-wp-theme-json-resolver.php
+++ b/lib/class-wp-theme-json-resolver.php
@@ -419,7 +419,7 @@ public static function get_merged_data( $settings = array(), $origin = 'user' )
 		$result->merge( self::get_theme_data( $theme_support_data ) );
 
 		if ( 'user' === $origin ) {
-			$result->merge( self::get_user_data() );
+			$result->merge( self::get_user_data(), 'update' );
 		}
 
 		return $result;
diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php
index eb723a7f0cf60b..994f2e413ddb50 100644
--- a/lib/class-wp-theme-json.php
+++ b/lib/class-wp-theme-json.php
@@ -1076,8 +1076,10 @@ public function get_stylesheet( $type = 'all' ) {
 	 * Merge new incoming data.
 	 *
 	 * @param WP_Theme_JSON $incoming Data to merge.
+	 * @param string $update_or_remove Whether update or remove existing colors
+	 *                                 for which the incoming data has a duplicated slug.
 	 */
-	public function merge( $incoming ) {
+	public function merge( $incoming, $update_or_remove = 'remove' ) {
 		$incoming_data = $incoming->get_raw_data();
 		$existing_data = $this->theme_json;
 
@@ -1095,11 +1097,16 @@ public function merge( $incoming ) {
 		$to_replace[] = array( 'typography', 'fontSizes' );
 		$to_replace[] = array( 'typography', 'fontFamilies' );
 
-		// Some others should be either appended to the existing
-		// or update the existing if the slug is the same.
+		// Some others should be appended to the existing.
+		// If the slug is the same than an existing element,
+		// the $update_or_remove param is used to decide
+		// what to do with the existing element:
+		// either remove it and append the incoming,
+		// or update it with the incoming.
 		$to_append   = array();
-		$to_append[] = array( 'color', 'palette' );
+		$to_append[] = array( 'color', 'duotone' );
 		$to_append[] = array( 'color', 'gradients' );
+		$to_append[] = array( 'color', 'palette' );
 
 		$nodes = self::get_setting_nodes( $this->theme_json );
 		foreach ( $nodes as $metadata ) {
@@ -1128,14 +1135,24 @@ public function merge( $incoming ) {
 					$merged[ $key ]                = $value;
 				}
 
+				$to_remove = [];
 				foreach( $incoming_node as $value ) {
-					if ( in_array( $value['slug'], $existing_slugs ) ) {
+					if ( ! in_array( $value['slug'], $existing_slugs ) ) {
+						$merged[] = $value;
+					} elseif ( 'update' === $update_or_remove ) {
 						$merged[ $index_table[ $value['slug'] ] ] = $value;
 					} else {
-						$merged[] = $value;
+						$merged[]    = $value;
+						$to_remove[] = $index_table[ $value['slug'] ];
 					}
 				}
 
+				// Remove the duplicated values and pack the sparsed array.
+				foreach( $to_remove as $index ) {
+					unset( $merged[ $index ] );
+				}
+				$merged = array_values( $merged );
+
 				gutenberg_experimental_set( $this->theme_json, $path, $merged );
 			}
 		}

From d45d090c99588ffa5c999e00eec2377e79a70b0c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andres.maneiro@automattic.com>
Date: Thu, 20 May 2021 09:40:56 +0200
Subject: [PATCH 10/10] Fix linting issues

---
 lib/class-wp-theme-json.php | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php
index 994f2e413ddb50..ba18c6a9e69247 100644
--- a/lib/class-wp-theme-json.php
+++ b/lib/class-wp-theme-json.php
@@ -1076,7 +1076,7 @@ public function get_stylesheet( $type = 'all' ) {
 	 * Merge new incoming data.
 	 *
 	 * @param WP_Theme_JSON $incoming Data to merge.
-	 * @param string $update_or_remove Whether update or remove existing colors
+	 * @param string        $update_or_remove Whether update or remove existing colors
 	 *                                 for which the incoming data has a duplicated slug.
 	 */
 	public function merge( $incoming, $update_or_remove = 'remove' ) {
@@ -1129,15 +1129,15 @@ public function merge( $incoming, $update_or_remove = 'remove' ) {
 				$index_table    = array();
 				$existing_slugs = array();
 				$merged         = array();
-				foreach( $existing_node as $key => $value ) {
+				foreach ( $existing_node as $key => $value ) {
 					$index_table[ $value['slug'] ] = $key;
 					$existing_slugs[]              = $value['slug'];
 					$merged[ $key ]                = $value;
 				}
 
-				$to_remove = [];
-				foreach( $incoming_node as $value ) {
-					if ( ! in_array( $value['slug'], $existing_slugs ) ) {
+				$to_remove = array();
+				foreach ( $incoming_node as $value ) {
+					if ( ! in_array( $value['slug'], $existing_slugs, true ) ) {
 						$merged[] = $value;
 					} elseif ( 'update' === $update_or_remove ) {
 						$merged[ $index_table[ $value['slug'] ] ] = $value;
@@ -1148,7 +1148,7 @@ public function merge( $incoming, $update_or_remove = 'remove' ) {
 				}
 
 				// Remove the duplicated values and pack the sparsed array.
-				foreach( $to_remove as $index ) {
+				foreach ( $to_remove as $index ) {
 					unset( $merged[ $index ] );
 				}
 				$merged = array_values( $merged );