diff --git a/packages/components/src/button/index.native.js b/packages/components/src/button/index.native.js
index add3778275f863..49a8a43c393cc6 100644
--- a/packages/components/src/button/index.native.js
+++ b/packages/components/src/button/index.native.js
@@ -93,15 +93,29 @@ export function Button( props ) {
label,
shortcut,
tooltipPosition,
+ isActiveStyle,
+ customContainerStyles,
} = props;
const preferredColorScheme = usePreferredColorScheme();
const isDisabled = ariaDisabled || disabled;
+ const containerStyle = [
+ styles.container,
+ customContainerStyles && { ...customContainerStyles },
+ ];
+
const buttonViewStyle = {
opacity: isDisabled ? 0.3 : 1,
...( fixedRatio && styles.fixedRatio ),
...( isPressed ? styles.buttonActive : styles.buttonInactive ),
+ ...( isPressed &&
+ isActiveStyle?.borderRadius && {
+ borderRadius: isActiveStyle.borderRadius,
+ } ),
+ ...( isActiveStyle?.backgroundColor && {
+ backgroundColor: isActiveStyle.backgroundColor,
+ } ),
};
const states = [];
@@ -159,7 +173,7 @@ export function Button( props ) {
accessibilityHint={ hint }
onPress={ onClick }
onLongPress={ onLongPress }
- style={ styles.container }
+ style={ containerStyle }
disabled={ isDisabled }
testID={ testID }
>
diff --git a/packages/components/src/mobile/color-settings/index.native.js b/packages/components/src/mobile/color-settings/index.native.js
index 2b898b7c63f906..9c877782b92822 100644
--- a/packages/components/src/mobile/color-settings/index.native.js
+++ b/packages/components/src/mobile/color-settings/index.native.js
@@ -28,6 +28,7 @@ const ColorSettingsMemo = memo(
gradientValue,
onGradientChange,
label,
+ hideNavigation,
} ) => {
useEffect( () => {
shouldEnableBottomSheetMaxHeight( true );
@@ -44,6 +45,7 @@ const ColorSettingsMemo = memo(
gradientValue,
onGradientChange,
label,
+ hideNavigation,
} }
>
diff --git a/packages/components/src/mobile/color-settings/palette.screen.native.js b/packages/components/src/mobile/color-settings/palette.screen.native.js
index f606b407b88326..9c6ae92486538a 100644
--- a/packages/components/src/mobile/color-settings/palette.screen.native.js
+++ b/packages/components/src/mobile/color-settings/palette.screen.native.js
@@ -26,7 +26,7 @@ import { colorsUtils } from './utils';
import styles from './style.scss';
-const HIT_SLOP = { top: 22, bottom: 22, left: 22, right: 22 };
+const HIT_SLOP = { top: 8, bottom: 8, left: 8, right: 8 };
const PaletteScreen = () => {
const route = useRoute();
@@ -38,6 +38,7 @@ const PaletteScreen = () => {
onGradientChange,
colorValue,
defaultSettings,
+ hideNavigation = false,
} = route.params || {};
const { segments, isGradient } = colorsUtils;
const [ currentValue, setCurrentValue ] = useState( colorValue );
@@ -164,10 +165,12 @@ const PaletteScreen = () => {
}
return (
-
-
- { label }
-
+ { ! hideNavigation && (
+
+
+ { label }
+
+ ) }
setIsAddingColor( true ), [
+ setIsAddingColor,
+ ] );
+ const disableIsAddingColor = useCallback( () => setIsAddingColor( false ), [
+ setIsAddingColor,
+ ] );
+ const colorIndicatorStyle = useMemo(
+ () =>
+ fillComputedColors(
+ contentRef,
+ getActiveColors( value, name, colors )
+ ),
+ [ value, colors ]
+ );
+
+ const hasColorsToChoose = ! isEmpty( colors ) || ! allowCustomControl;
+
+ const onPressButton = useCallback( () => {
+ if ( hasColorsToChoose ) {
+ enableIsAddingColor();
+ } else {
+ onChange( removeFormat( value, name ) );
+ }
+ }, [ hasColorsToChoose, value ] );
+
+ const outlineStyle = usePreferredColorSchemeStyle(
+ styles[ 'components-inline-color__outline' ],
+ styles[ 'components-inline-color__outline--dark' ]
+ );
+
+ if ( ! hasColorsToChoose && ! isActive ) {
+ return null;
+ }
+
+ const isActiveStyle = {
+ ...colorIndicatorStyle,
+ ...( ! colorIndicatorStyle?.backgroundColor
+ ? { backgroundColor: 'transparent' }
+ : {} ),
+ ...styles[ 'components-inline-color--is-active' ],
+ };
+
+ const customContainerStyles =
+ styles[ 'components-inline-color__button-container' ];
+
+ return (
+ <>
+
+
+ { isActive && (
+
+ ) }
+
+
+ }
+ title={ title }
+ extraProps={ {
+ isActiveStyle,
+ customContainerStyles,
+ } }
+ // If has no colors to choose but a color is active remove the color onClick
+ onClick={ onPressButton }
+ />
+
+
+ { isAddingColor && (
+
+ ) }
+ >
+ );
+}
+
+export const textColor = {
+ name,
+ title,
+ tagName: 'mark',
+ className: 'has-inline-color',
+ attributes: {
+ style: 'style',
+ class: 'class',
+ },
+ /*
+ * Since this format relies on the tag, it's important to
+ * prevent the default yellow background color applied by most
+ * browsers. The solution is to detect when this format is used with a
+ * text color but no background color, and in such cases to override
+ * the default styling with a transparent background.
+ *
+ * @see https://github.com/WordPress/gutenberg/pull/35516
+ */
+ __unstableFilterAttributeValue( key, value ) {
+ if ( key !== 'style' ) return value;
+ // We should not add a background-color if it's already set
+ if ( value && value.includes( 'background-color' ) ) return value;
+ const addedCSS = [ 'background-color', transparentValue ].join( ':' );
+ // Prepend `addedCSS` to avoid a double `;;` as any the existing CSS
+ // rules will already include a `;`.
+ return value ? [ addedCSS, value ].join( ';' ) : addedCSS;
+ },
+ edit: TextColorEdit,
+};
diff --git a/packages/format-library/src/text-color/inline.js b/packages/format-library/src/text-color/inline.js
index 37f2d6f9401f63..03a80fb18e6871 100644
--- a/packages/format-library/src/text-color/inline.js
+++ b/packages/format-library/src/text-color/inline.js
@@ -42,7 +42,7 @@ function parseCSS( css = '' ) {
}, {} );
}
-function parseClassName( className = '', colorSettings ) {
+export function parseClassName( className = '', colorSettings ) {
return className.split( ' ' ).reduce( ( accumulator, name ) => {
// `colorSlug` could contain dashes, so simply match the start and end.
if ( name.startsWith( 'has-' ) && name.endsWith( '-color' ) ) {
diff --git a/packages/format-library/src/text-color/inline.native.js b/packages/format-library/src/text-color/inline.native.js
new file mode 100644
index 00000000000000..efbf6db545e5dd
--- /dev/null
+++ b/packages/format-library/src/text-color/inline.native.js
@@ -0,0 +1,163 @@
+/**
+ * WordPress dependencies
+ */
+import { useCallback, useMemo } from '@wordpress/element';
+import {
+ applyFormat,
+ removeFormat,
+ getActiveFormat,
+} from '@wordpress/rich-text';
+import {
+ useSetting,
+ getColorClassName,
+ getColorObjectByColorValue,
+} from '@wordpress/block-editor';
+import { BottomSheet, ColorSettings } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import { textColor as settings } from './index';
+import { transparentValue } from './index.js';
+import { parseClassName } from './inline.js';
+
+function parseCSS( css = '' ) {
+ return css.split( ';' ).reduce( ( accumulator, rule ) => {
+ if ( rule ) {
+ const [ property, value ] = rule.replace( / /g, '' ).split( ':' );
+ if ( property === 'color' ) accumulator.color = value;
+ if ( property === 'background-color' && value !== transparentValue )
+ accumulator.backgroundColor = value;
+ }
+ return accumulator;
+ }, {} );
+}
+
+function getActiveColors( value, name, colorSettings ) {
+ const activeColorFormat = getActiveFormat( value, name );
+
+ if ( ! activeColorFormat ) {
+ return {};
+ }
+
+ return {
+ ...parseCSS( activeColorFormat.attributes.style ),
+ ...parseClassName( activeColorFormat.attributes.class, colorSettings ),
+ };
+}
+
+function setColors( value, name, colorSettings, colors ) {
+ const { color, backgroundColor } = {
+ ...getActiveColors( value, name, colorSettings ),
+ ...colors,
+ };
+
+ if ( ! color && ! backgroundColor ) {
+ return removeFormat( value, name );
+ }
+
+ const styles = [];
+ const classNames = [];
+ const attributes = {};
+
+ if ( backgroundColor ) {
+ styles.push( [ 'background-color', backgroundColor ].join( ':' ) );
+ } else {
+ // Override default browser color for mark element.
+ styles.push( [ 'background-color', transparentValue ].join( ':' ) );
+ }
+
+ if ( color ) {
+ const colorObject = getColorObjectByColorValue( colorSettings, color );
+
+ if ( colorObject ) {
+ classNames.push( getColorClassName( 'color', colorObject.slug ) );
+ styles.push( [ 'color', colorObject.color ].join( ':' ) );
+ } else {
+ styles.push( [ 'color', color ].join( ':' ) );
+ }
+ }
+
+ if ( styles.length ) attributes.style = styles.join( ';' );
+ if ( classNames.length ) attributes.class = classNames.join( ' ' );
+
+ const format = { type: name, attributes };
+
+ // For cases when there is no text selected, formatting is forced
+ // for the first empty character
+ if (
+ value?.start === value?.end &&
+ ( value?.text.length === 0 ||
+ value.text?.charAt( value.end - 1 ) === ' ' )
+ ) {
+ return applyFormat( value, format, value?.start - 1, value?.end + 1 );
+ }
+
+ return applyFormat( value, format );
+}
+
+function ColorPicker( { name, value, onChange } ) {
+ const property = 'color';
+ const colors = useSetting( 'color.palette' ) || settings.colors;
+ const colorSettings = {
+ colors,
+ };
+
+ const onColorChange = useCallback(
+ ( color ) => {
+ if ( color !== '' ) {
+ onChange(
+ setColors( value, name, colors, { [ property ]: color } )
+ );
+ // Remove formatting if the color was reset, there's no
+ // current selection and the previous character is a space
+ } else if (
+ value?.start === value?.end &&
+ value.text?.charAt( value?.end - 1 ) === ' '
+ ) {
+ onChange(
+ removeFormat( value, name, value.end - 1, value.end )
+ );
+ } else {
+ onChange( removeFormat( value, name ) );
+ }
+ },
+ [ colors, onChange, property ]
+ );
+ const activeColors = useMemo(
+ () => getActiveColors( value, name, colors ),
+ [ name, value, colors ]
+ );
+
+ return (
+
+ );
+}
+
+export default function InlineColorUI( { name, value, onChange, onClose } ) {
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/format-library/src/text-color/style.native.scss b/packages/format-library/src/text-color/style.native.scss
new file mode 100644
index 00000000000000..43e84e81ea7e99
--- /dev/null
+++ b/packages/format-library/src/text-color/style.native.scss
@@ -0,0 +1,23 @@
+.components-inline-color--is-active {
+ border-radius: 18px;
+}
+
+.components-inline-color__outline {
+ border-color: $light-dim;
+ top: 6px;
+ bottom: 6px;
+ left: 11px;
+ right: 11px;
+ border-radius: 24px;
+ border-width: $border-width;
+ position: absolute;
+ z-index: 2;
+}
+
+.components-inline-color__outline--dark {
+ border-color: $dark-ultra-dim;
+}
+
+.components-inline-color__button-container {
+ padding: 6px;
+}
diff --git a/packages/react-native-aztec/RNTAztecView.podspec b/packages/react-native-aztec/RNTAztecView.podspec
index cdb8deed1e12c9..970a8b6af6a709 100644
--- a/packages/react-native-aztec/RNTAztecView.podspec
+++ b/packages/react-native-aztec/RNTAztecView.podspec
@@ -18,6 +18,6 @@ Pod::Spec.new do |s|
s.xcconfig = {'OTHER_LDFLAGS' => '-lxml2',
'HEADER_SEARCH_PATHS' => '/usr/include/libxml2'}
s.dependency 'React-Core'
- s.dependency 'WordPress-Aztec-iOS', '~> 1.19.5'
+ s.dependency 'WordPress-Aztec-iOS', '~> 1.19.6'
end
diff --git a/packages/react-native-aztec/android/build.gradle b/packages/react-native-aztec/android/build.gradle
index 1d207cd6bd7baf..27d4e4de038851 100644
--- a/packages/react-native-aztec/android/build.gradle
+++ b/packages/react-native-aztec/android/build.gradle
@@ -9,7 +9,7 @@ buildscript {
jSoupVersion = '1.10.3'
wordpressUtilsVersion = '1.22'
espressoVersion = '3.0.1'
- aztecVersion = 'v1.5.1'
+ aztecVersion = 'v1.5.2'
willPublishReactNativeAztecBinary = properties["willPublishReactNativeAztecBinary"]?.toBoolean() ?: false
}
}
diff --git a/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java b/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java
index cb702492349a8d..13340484b0ab9a 100644
--- a/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java
+++ b/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecText.java
@@ -93,6 +93,7 @@ public class ReactAztecText extends AztecText {
put(AztecTextFormat.FORMAT_CITE, "italic");
put(AztecTextFormat.FORMAT_STRIKETHROUGH, "strikethrough");
put(AztecTextFormat.FORMAT_UNDERLINE, "underline");
+ put(AztecTextFormat.FORMAT_MARK, "mark");
}
};
@@ -327,6 +328,9 @@ private void updateToolbarButtons(ArrayList appliedStyles) {
if (currentStyle == AztecTextFormat.FORMAT_STRIKETHROUGH) {
formattingOptions.add("strikethrough");
}
+ if (currentStyle == AztecTextFormat.FORMAT_MARK) {
+ formattingOptions.add("mark");
+ }
}
// Check if the same formatting event was already sent
@@ -535,6 +539,8 @@ public void setActiveFormats(Iterable newFormats) {
break;
case "underline":
newFormatsSet.add(AztecTextFormat.FORMAT_UNDERLINE);
+ case "mark":
+ newFormatsSet.add(AztecTextFormat.FORMAT_MARK);
break;
}
}
diff --git a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift
index cdd28ea38d39eb..8e94be11f8323e 100644
--- a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift
+++ b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift
@@ -121,6 +121,7 @@ class RCTAztecView: Aztec.TextView {
.italic: "italic",
.strikethrough: "strikethrough",
.link: "link",
+ .mark: "mark"
]
override init(defaultFont: UIFont, defaultParagraphStyle: ParagraphStyle, defaultMissingImage: UIImage) {
@@ -689,6 +690,7 @@ class RCTAztecView: Aztec.TextView {
case "bold": toggleBold(range: emptyRange)
case "italic": toggleItalic(range: emptyRange)
case "strikethrough": toggleStrikethrough(range: emptyRange)
+ case "mark": toggleMark(range: emptyRange)
default: print("Format not recognized")
}
}
diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md
index c88168127c07a2..4c4ec357ee9406 100644
--- a/packages/react-native-editor/CHANGELOG.md
+++ b/packages/react-native-editor/CHANGELOG.md
@@ -12,6 +12,7 @@ For each user feature we should also add a importance categorization label to i
## Unreleased
- [**] [iOS] Fix scroll update when typing in RichText component [#36914]
- [*] [Preformatted block] Fix an issue where the background color is not showing up for standard themes. [#36883]
+- [***] Highlight text - enables color customization for specific text within a Pragraph block [#36028]
## 1.67.0
- [**] Adds Clipboard Link Suggestion to Image block and Button block [#35972]
diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock
index 4577ee4de87ba9..83d654fab35a05 100644
--- a/packages/react-native-editor/ios/Podfile.lock
+++ b/packages/react-native-editor/ios/Podfile.lock
@@ -305,8 +305,8 @@ PODS:
- React-Core
- RNTAztecView (1.67.0):
- React-Core
- - WordPress-Aztec-iOS (~> 1.19.5)
- - WordPress-Aztec-iOS (1.19.5)
+ - WordPress-Aztec-iOS (~> 1.19.6)
+ - WordPress-Aztec-iOS (1.19.6)
- Yoga (1.14.0)
DEPENDENCIES:
@@ -457,7 +457,7 @@ SPEC CHECKSUMS:
BVLinearGradient: 1e5474c982efcfcaed47f368a61431bb38a4faf8
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
FBLazyVector: 49cbe4b43e445b06bf29199b6ad2057649e4c8f5
- FBReactNativeSpec: ee3fc80110975e231c8537d2e434a8afabe66fdc
+ FBReactNativeSpec: 80e9cf1155002ee4720084d8813326d913815e2f
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
Gutenberg: cb22fce31133194d87ce74b3f3d45ebf91b585cf
RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c
@@ -496,8 +496,8 @@ SPEC CHECKSUMS:
RNReanimated: 39a9478eb635667c9a4da08ac906add9901b145e
RNScreens: 185dcb481fab2f3dc77413f62b43dc3df826029c
RNSVG: 9c0db12736608e32841e90fe9773db70ea40de20
- RNTAztecView: 59ba50af03168074634543b40595a9c29afa39ff
- WordPress-Aztec-iOS: af36d9cb86a0109b568f516874870e2801ba1bd9
+ RNTAztecView: 28dd2b1cdb74cb8161d76a7c1defbda1ad2f737a
+ WordPress-Aztec-iOS: 129276e7d1391acb08157641a54394668bb0d7f8
Yoga: 8c8436d4171c87504c648ae23b1d81242bdf3bbf
PODFILE CHECKSUM: db5f67a29ecba75541dad181ff59246b6da2fb09
diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js
index 860a1448f25c35..98491637663629 100644
--- a/packages/rich-text/src/component/index.native.js
+++ b/packages/rich-text/src/component/index.native.js
@@ -42,6 +42,7 @@ import { toHTMLString } from '../to-html-string';
import { removeLineSeparator } from '../remove-line-separator';
import { isCollapsed } from '../is-collapsed';
import { remove } from '../remove';
+import { getFormatColors } from '../get-format-colors';
import styles from './style.scss';
import ToolbarButtonWithOptions from './toolbar-button-with-options';
@@ -53,6 +54,7 @@ const gutenbergFormatNamesToAztec = {
'core/bold': 'bold',
'core/italic': 'italic',
'core/strikethrough': 'strikethrough',
+ 'core/text-color': 'mark',
};
const EMPTY_PARAGRAPH_TAGS = '';
@@ -143,13 +145,26 @@ export class RichText extends Component {
* @return {Object} The current record (value and selection).
*/
getRecord() {
- const { selectionStart: start, selectionEnd: end } = this.props;
+ const {
+ selectionStart: start,
+ selectionEnd: end,
+ colorPalette,
+ } = this.props;
const { value } = this.props;
+ const currentValue = this.formatToValue( value );
- const { formats, replacements, text } = this.formatToValue( value );
+ const { formats, replacements, text } = currentValue;
const { activeFormats } = this.state;
+ const newFormats = getFormatColors( value, formats, colorPalette );
- return { formats, replacements, text, start, end, activeFormats };
+ return {
+ formats: newFormats,
+ replacements,
+ text,
+ start,
+ end,
+ activeFormats,
+ };
}
/**
@@ -1113,6 +1128,7 @@ export class RichText extends Component {
{ isSelected && (
<>
{
+ format.forEach( ( currentFormat ) => {
+ if ( currentFormat?.type === FORMAT_TYPE ) {
+ const className = currentFormat?.attributes?.class;
+ currentFormat.attributes.style = currentFormat.attributes.style.replace(
+ / /g,
+ ''
+ );
+
+ className?.split( ' ' ).forEach( ( currentClass ) => {
+ const match = currentClass.match( REGEX_TO_MATCH );
+ if ( match ) {
+ const [ , colorSlug ] = currentClass.match(
+ REGEX_TO_MATCH
+ );
+ const colorObject = getColorObjectByAttributeValues(
+ colors,
+ colorSlug
+ );
+ const currentStyles =
+ currentFormat?.attributes?.style;
+ if (
+ colorObject &&
+ ( ! currentStyles ||
+ currentStyles?.indexOf(
+ colorObject.color
+ ) === -1 )
+ ) {
+ currentFormat.attributes.style = [
+ `color: ${ colorObject.color }`,
+ currentStyles,
+ ].join( ';' );
+ }
+ }
+ } );
+ }
+ } );
+ } );
+
+ return newFormats;
+ }
+
+ return formats;
+}