@wordpress/format-library 3.0.19 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/LICENSE.md +1 -1
- package/build/default-formats.native.js +3 -1
- package/build/default-formats.native.js.map +1 -1
- package/build/link/index.native.js +2 -2
- package/build/link/index.native.js.map +1 -1
- package/build/link/modal-screens/link-picker-screen.native.js +12 -25
- package/build/link/modal-screens/link-picker-screen.native.js.map +1 -1
- package/build/link/modal-screens/link-settings-screen.native.js +50 -19
- package/build/link/modal-screens/link-settings-screen.native.js.map +1 -1
- package/build/text-color/index.native.js +192 -0
- package/build/text-color/index.native.js.map +1 -0
- package/build/text-color/inline.js +1 -0
- package/build/text-color/inline.js.map +1 -1
- package/build/text-color/inline.native.js +191 -0
- package/build/text-color/inline.native.js.map +1 -0
- package/build-module/default-formats.native.js +2 -1
- package/build-module/default-formats.native.js.map +1 -1
- package/build-module/link/index.native.js +1 -1
- package/build-module/link/index.native.js.map +1 -1
- package/build-module/link/modal-screens/link-picker-screen.native.js +12 -26
- package/build-module/link/modal-screens/link-picker-screen.native.js.map +1 -1
- package/build-module/link/modal-screens/link-settings-screen.native.js +50 -19
- package/build-module/link/modal-screens/link-settings-screen.native.js.map +1 -1
- package/build-module/text-color/index.native.js +173 -0
- package/build-module/text-color/index.native.js.map +1 -0
- package/build-module/text-color/inline.js +1 -2
- package/build-module/text-color/inline.js.map +1 -1
- package/build-module/text-color/inline.native.js +177 -0
- package/build-module/text-color/inline.native.js.map +1 -0
- package/package.json +15 -15
- package/src/default-formats.native.js +2 -1
- package/src/link/index.native.js +1 -1
- package/src/link/modal-screens/link-picker-screen.native.js +12 -23
- package/src/link/modal-screens/link-settings-screen.native.js +27 -14
- package/src/link/test/index.native.js +137 -0
- package/src/link/test/modal.native.js +3 -3
- package/src/text-color/index.native.js +194 -0
- package/src/text-color/inline.js +1 -1
- package/src/text-color/inline.native.js +186 -0
- package/src/text-color/style.native.scss +23 -0
- package/src/text-color/test/__snapshots__/index.native.js.snap +19 -0
- package/src/text-color/test/index.native.js +164 -0
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { useNavigation, useRoute } from '@react-navigation/native';
|
|
5
|
+
|
|
5
6
|
/**
|
|
6
7
|
* WordPress dependencies
|
|
7
8
|
*/
|
|
8
9
|
import { __ } from '@wordpress/i18n';
|
|
9
10
|
import { useState, useContext, useEffect, useMemo } from '@wordpress/element';
|
|
10
11
|
import { prependHTTP } from '@wordpress/url';
|
|
11
|
-
|
|
12
12
|
import { BottomSheet, BottomSheetContext } from '@wordpress/components';
|
|
13
13
|
import {
|
|
14
14
|
create,
|
|
@@ -62,16 +62,19 @@ const LinkSettingsScreen = ( {
|
|
|
62
62
|
};
|
|
63
63
|
useEffect( () => {
|
|
64
64
|
onHandleClosingBottomSheet( () => {
|
|
65
|
-
submit( inputValue );
|
|
65
|
+
submit( inputValue, { skipStateUpdates: true } );
|
|
66
66
|
} );
|
|
67
67
|
}, [ inputValue, opensInNewWindow, text ] );
|
|
68
68
|
|
|
69
69
|
useEffect( () => {
|
|
70
70
|
const { isActiveLink, isRemovingLink } = linkValues;
|
|
71
71
|
if ( !! inputValue && ! isActiveLink && isVisible ) {
|
|
72
|
-
submitLink( false );
|
|
73
|
-
} else if (
|
|
74
|
-
|
|
72
|
+
submitLink( { shouldCloseBottomSheet: false } );
|
|
73
|
+
} else if (
|
|
74
|
+
( ( inputValue === '' && isActiveLink ) || isRemovingLink ) &&
|
|
75
|
+
isVisible
|
|
76
|
+
) {
|
|
77
|
+
removeLink( { shouldCloseBottomSheet: false } );
|
|
75
78
|
}
|
|
76
79
|
}, [
|
|
77
80
|
inputValue,
|
|
@@ -80,12 +83,17 @@ const LinkSettingsScreen = ( {
|
|
|
80
83
|
linkValues.isRemovingLink,
|
|
81
84
|
] );
|
|
82
85
|
|
|
83
|
-
const clearFormat = () => {
|
|
86
|
+
const clearFormat = ( { skipStateUpdates = false } = {} ) => {
|
|
84
87
|
onChange( { ...value, activeFormats: [] } );
|
|
85
|
-
|
|
88
|
+
if ( ! skipStateUpdates ) {
|
|
89
|
+
setLinkValues( { isActiveLink: false, isRemovingLink: true } );
|
|
90
|
+
}
|
|
86
91
|
};
|
|
87
92
|
|
|
88
|
-
const submitLink = (
|
|
93
|
+
const submitLink = ( {
|
|
94
|
+
shouldCloseBottomSheet = true,
|
|
95
|
+
skipStateUpdates = false,
|
|
96
|
+
} = {} ) => {
|
|
89
97
|
const url = prependHTTP( inputValue );
|
|
90
98
|
const linkText = text || inputValue;
|
|
91
99
|
const format = createLinkFormat( {
|
|
@@ -127,7 +135,9 @@ const LinkSettingsScreen = ( {
|
|
|
127
135
|
}
|
|
128
136
|
newAttributes.activeFormats = [];
|
|
129
137
|
onChange( { ...newAttributes, needsSelectionUpdate: true } );
|
|
130
|
-
|
|
138
|
+
if ( ! skipStateUpdates ) {
|
|
139
|
+
setLinkValues( { isActiveLink: true, isRemovingLink: false } );
|
|
140
|
+
}
|
|
131
141
|
|
|
132
142
|
if ( ! isValidHref( url ) ) {
|
|
133
143
|
speak(
|
|
@@ -147,19 +157,22 @@ const LinkSettingsScreen = ( {
|
|
|
147
157
|
}
|
|
148
158
|
};
|
|
149
159
|
|
|
150
|
-
const removeLink = (
|
|
151
|
-
|
|
160
|
+
const removeLink = ( {
|
|
161
|
+
shouldCloseBottomSheet = true,
|
|
162
|
+
skipStateUpdates = false,
|
|
163
|
+
} = {} ) => {
|
|
164
|
+
clearFormat( { skipStateUpdates } );
|
|
152
165
|
onRemove();
|
|
153
166
|
if ( shouldCloseBottomSheet ) {
|
|
154
167
|
onClose();
|
|
155
168
|
}
|
|
156
169
|
};
|
|
157
170
|
|
|
158
|
-
const submit = ( submitValue ) => {
|
|
171
|
+
const submit = ( submitValue, { skipStateUpdates = false } = {} ) => {
|
|
159
172
|
if ( submitValue === '' ) {
|
|
160
|
-
removeLink();
|
|
173
|
+
removeLink( { skipStateUpdates } );
|
|
161
174
|
} else {
|
|
162
|
-
submitLink();
|
|
175
|
+
submitLink( { skipStateUpdates } );
|
|
163
176
|
}
|
|
164
177
|
};
|
|
165
178
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { Keyboard, Platform } from 'react-native';
|
|
5
|
+
import { render, fireEvent, waitFor } from 'test/helpers';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WordPress dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { Slot, SlotFillProvider } from '@wordpress/components';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies
|
|
14
|
+
*/
|
|
15
|
+
import { link } from '../index';
|
|
16
|
+
|
|
17
|
+
const { edit: LinkEdit } = link;
|
|
18
|
+
|
|
19
|
+
// Simplified tree to render link format component
|
|
20
|
+
const LinkEditSlot = ( props ) => (
|
|
21
|
+
<SlotFillProvider>
|
|
22
|
+
<Slot name="RichText.ToolbarControls.link" />
|
|
23
|
+
<LinkEdit { ...props } />
|
|
24
|
+
</SlotFillProvider>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
jest.useFakeTimers( 'legacy' );
|
|
28
|
+
jest.spyOn( Keyboard, 'dismiss' );
|
|
29
|
+
|
|
30
|
+
describe( 'Android', () => {
|
|
31
|
+
it( 'improves back animation performance by dismissing keyboard beforehand', async () => {
|
|
32
|
+
const screen = render(
|
|
33
|
+
<LinkEditSlot
|
|
34
|
+
activeAttributes={ {} }
|
|
35
|
+
onChange={ () => {} }
|
|
36
|
+
value={ {
|
|
37
|
+
text: '',
|
|
38
|
+
formats: [],
|
|
39
|
+
replacements: [],
|
|
40
|
+
} }
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
fireEvent.press( screen.getByA11yLabel( 'Link' ) );
|
|
44
|
+
fireEvent.press(
|
|
45
|
+
screen.getByA11yLabel( 'Link to, Search or type URL' )
|
|
46
|
+
);
|
|
47
|
+
// Await back button to allow async state updates to complete
|
|
48
|
+
const backButton = await waitFor( () =>
|
|
49
|
+
screen.getByA11yLabel( 'Go back' )
|
|
50
|
+
);
|
|
51
|
+
Keyboard.dismiss.mockClear();
|
|
52
|
+
fireEvent.press( backButton );
|
|
53
|
+
|
|
54
|
+
expect( Keyboard.dismiss ).toHaveBeenCalledTimes( 1 );
|
|
55
|
+
} );
|
|
56
|
+
|
|
57
|
+
it( 'improves apply animation performance by dismissing keyboard beforehand', async () => {
|
|
58
|
+
const { getByA11yLabel } = render(
|
|
59
|
+
<LinkEditSlot
|
|
60
|
+
activeAttributes={ {} }
|
|
61
|
+
onChange={ () => {} }
|
|
62
|
+
value={ {
|
|
63
|
+
text: '',
|
|
64
|
+
formats: [],
|
|
65
|
+
replacements: [],
|
|
66
|
+
} }
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
fireEvent.press( getByA11yLabel( 'Link' ) );
|
|
70
|
+
fireEvent.press( getByA11yLabel( 'Link to, Search or type URL' ) );
|
|
71
|
+
// Await back button to allow async state updates to complete
|
|
72
|
+
const backButton = await waitFor( () => getByA11yLabel( 'Apply' ) );
|
|
73
|
+
Keyboard.dismiss.mockClear();
|
|
74
|
+
fireEvent.press( backButton );
|
|
75
|
+
|
|
76
|
+
expect( Keyboard.dismiss ).toHaveBeenCalledTimes( 1 );
|
|
77
|
+
} );
|
|
78
|
+
} );
|
|
79
|
+
|
|
80
|
+
describe( 'iOS', () => {
|
|
81
|
+
const originalPlatform = Platform.OS;
|
|
82
|
+
beforeAll( () => {
|
|
83
|
+
Platform.OS = 'ios';
|
|
84
|
+
} );
|
|
85
|
+
|
|
86
|
+
afterAll( () => {
|
|
87
|
+
Platform.OS = originalPlatform;
|
|
88
|
+
} );
|
|
89
|
+
|
|
90
|
+
it( 'improves back animation performance by dismissing keyboard beforehand', async () => {
|
|
91
|
+
const screen = render(
|
|
92
|
+
<LinkEditSlot
|
|
93
|
+
activeAttributes={ {} }
|
|
94
|
+
onChange={ () => {} }
|
|
95
|
+
value={ {
|
|
96
|
+
text: '',
|
|
97
|
+
formats: [],
|
|
98
|
+
replacements: [],
|
|
99
|
+
} }
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
fireEvent.press( screen.getByA11yLabel( 'Link' ) );
|
|
103
|
+
fireEvent.press(
|
|
104
|
+
screen.getByA11yLabel( 'Link to, Search or type URL' )
|
|
105
|
+
);
|
|
106
|
+
// Await back button to allow async state updates to complete
|
|
107
|
+
const backButton = await waitFor( () =>
|
|
108
|
+
screen.getByA11yLabel( 'Go back' )
|
|
109
|
+
);
|
|
110
|
+
Keyboard.dismiss.mockClear();
|
|
111
|
+
fireEvent.press( backButton );
|
|
112
|
+
|
|
113
|
+
expect( Keyboard.dismiss ).toHaveBeenCalledTimes( 1 );
|
|
114
|
+
} );
|
|
115
|
+
|
|
116
|
+
it( 'improves apply animation performance by dismissing keyboard beforehand', async () => {
|
|
117
|
+
const { getByA11yLabel } = render(
|
|
118
|
+
<LinkEditSlot
|
|
119
|
+
activeAttributes={ {} }
|
|
120
|
+
onChange={ () => {} }
|
|
121
|
+
value={ {
|
|
122
|
+
text: '',
|
|
123
|
+
formats: [],
|
|
124
|
+
replacements: [],
|
|
125
|
+
} }
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
fireEvent.press( getByA11yLabel( 'Link' ) );
|
|
129
|
+
fireEvent.press( getByA11yLabel( 'Link to, Search or type URL' ) );
|
|
130
|
+
// Await back button to allow async state updates to complete
|
|
131
|
+
const backButton = await waitFor( () => getByA11yLabel( 'Apply' ) );
|
|
132
|
+
Keyboard.dismiss.mockClear();
|
|
133
|
+
fireEvent.press( backButton );
|
|
134
|
+
|
|
135
|
+
expect( Keyboard.dismiss ).toHaveBeenCalledTimes( 1 );
|
|
136
|
+
} );
|
|
137
|
+
} );
|
|
@@ -5,11 +5,11 @@ import ModalLinkUI from '../modal';
|
|
|
5
5
|
/**
|
|
6
6
|
* External dependencies
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { render } from 'test/helpers';
|
|
9
9
|
|
|
10
10
|
describe( 'LinksUI', () => {
|
|
11
11
|
it( 'LinksUI renders', () => {
|
|
12
|
-
const
|
|
13
|
-
expect(
|
|
12
|
+
const screen = render( <ModalLinkUI /> );
|
|
13
|
+
expect( screen.container ).toBeTruthy();
|
|
14
14
|
} );
|
|
15
15
|
} );
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { isEmpty } from 'lodash';
|
|
5
|
+
import { View } from 'react-native';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WordPress dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { __ } from '@wordpress/i18n';
|
|
11
|
+
import { useCallback, useMemo, useState } from '@wordpress/element';
|
|
12
|
+
import { BlockControls, useSetting } from '@wordpress/block-editor';
|
|
13
|
+
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
|
|
14
|
+
import { Icon, textColor as textColorIcon } from '@wordpress/icons';
|
|
15
|
+
import { removeFormat } from '@wordpress/rich-text';
|
|
16
|
+
import { usePreferredColorSchemeStyle } from '@wordpress/compose';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Internal dependencies
|
|
20
|
+
*/
|
|
21
|
+
import { getActiveColors } from './inline.js';
|
|
22
|
+
import { transparentValue } from './index.js';
|
|
23
|
+
import { default as InlineColorUI } from './inline';
|
|
24
|
+
import styles from './style.scss';
|
|
25
|
+
|
|
26
|
+
const name = 'core/text-color';
|
|
27
|
+
const title = __( 'Text color' );
|
|
28
|
+
|
|
29
|
+
const EMPTY_ARRAY = [];
|
|
30
|
+
|
|
31
|
+
function getComputedStyleProperty( element, property ) {
|
|
32
|
+
const {
|
|
33
|
+
props: { style = {} },
|
|
34
|
+
} = element;
|
|
35
|
+
|
|
36
|
+
if ( property === 'background-color' ) {
|
|
37
|
+
const { backgroundColor, baseColors } = style;
|
|
38
|
+
|
|
39
|
+
if ( backgroundColor !== 'transparent' ) {
|
|
40
|
+
return backgroundColor;
|
|
41
|
+
} else if ( baseColors && baseColors?.color?.background ) {
|
|
42
|
+
return baseColors?.color?.background;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return 'transparent';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function fillComputedColors( element, { color, backgroundColor } ) {
|
|
50
|
+
if ( ! color && ! backgroundColor ) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
color: color || getComputedStyleProperty( element, 'color' ),
|
|
56
|
+
backgroundColor: getComputedStyleProperty(
|
|
57
|
+
element,
|
|
58
|
+
'background-color'
|
|
59
|
+
),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function TextColorEdit( {
|
|
64
|
+
value,
|
|
65
|
+
onChange,
|
|
66
|
+
isActive,
|
|
67
|
+
activeAttributes,
|
|
68
|
+
contentRef,
|
|
69
|
+
} ) {
|
|
70
|
+
const allowCustomControl = useSetting( 'color.custom' );
|
|
71
|
+
const colors = useSetting( 'color.palette' ) || EMPTY_ARRAY;
|
|
72
|
+
const [ isAddingColor, setIsAddingColor ] = useState( false );
|
|
73
|
+
const enableIsAddingColor = useCallback( () => setIsAddingColor( true ), [
|
|
74
|
+
setIsAddingColor,
|
|
75
|
+
] );
|
|
76
|
+
const disableIsAddingColor = useCallback( () => setIsAddingColor( false ), [
|
|
77
|
+
setIsAddingColor,
|
|
78
|
+
] );
|
|
79
|
+
const colorIndicatorStyle = useMemo(
|
|
80
|
+
() =>
|
|
81
|
+
fillComputedColors(
|
|
82
|
+
contentRef,
|
|
83
|
+
getActiveColors( value, name, colors )
|
|
84
|
+
),
|
|
85
|
+
[ value, colors ]
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const hasColorsToChoose = ! isEmpty( colors ) || ! allowCustomControl;
|
|
89
|
+
|
|
90
|
+
const onPressButton = useCallback( () => {
|
|
91
|
+
if ( hasColorsToChoose ) {
|
|
92
|
+
enableIsAddingColor();
|
|
93
|
+
} else {
|
|
94
|
+
onChange( removeFormat( value, name ) );
|
|
95
|
+
}
|
|
96
|
+
}, [ hasColorsToChoose, value ] );
|
|
97
|
+
|
|
98
|
+
const outlineStyle = usePreferredColorSchemeStyle(
|
|
99
|
+
styles[ 'components-inline-color__outline' ],
|
|
100
|
+
styles[ 'components-inline-color__outline--dark' ]
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if ( ! hasColorsToChoose && ! isActive ) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const isActiveStyle = {
|
|
108
|
+
...colorIndicatorStyle,
|
|
109
|
+
...( ! colorIndicatorStyle?.backgroundColor
|
|
110
|
+
? { backgroundColor: 'transparent' }
|
|
111
|
+
: {} ),
|
|
112
|
+
...styles[ 'components-inline-color--is-active' ],
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const customContainerStyles =
|
|
116
|
+
styles[ 'components-inline-color__button-container' ];
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<>
|
|
120
|
+
<BlockControls>
|
|
121
|
+
<ToolbarGroup>
|
|
122
|
+
{ isActive && (
|
|
123
|
+
<View style={ outlineStyle } pointerEvents="none" />
|
|
124
|
+
) }
|
|
125
|
+
|
|
126
|
+
<ToolbarButton
|
|
127
|
+
name="text-color"
|
|
128
|
+
isActive={ isActive }
|
|
129
|
+
icon={
|
|
130
|
+
<Icon
|
|
131
|
+
icon={ textColorIcon }
|
|
132
|
+
style={
|
|
133
|
+
colorIndicatorStyle?.color && {
|
|
134
|
+
color: colorIndicatorStyle.color,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
138
|
+
}
|
|
139
|
+
title={ title }
|
|
140
|
+
extraProps={ {
|
|
141
|
+
isActiveStyle,
|
|
142
|
+
customContainerStyles,
|
|
143
|
+
} }
|
|
144
|
+
// If has no colors to choose but a color is active remove the color onClick
|
|
145
|
+
onClick={ onPressButton }
|
|
146
|
+
/>
|
|
147
|
+
</ToolbarGroup>
|
|
148
|
+
</BlockControls>
|
|
149
|
+
{ isAddingColor && (
|
|
150
|
+
<InlineColorUI
|
|
151
|
+
name={ name }
|
|
152
|
+
onClose={ disableIsAddingColor }
|
|
153
|
+
activeAttributes={ activeAttributes }
|
|
154
|
+
value={ value }
|
|
155
|
+
onChange={ onChange }
|
|
156
|
+
contentRef={ contentRef }
|
|
157
|
+
/>
|
|
158
|
+
) }
|
|
159
|
+
</>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const textColor = {
|
|
164
|
+
name,
|
|
165
|
+
title,
|
|
166
|
+
tagName: 'mark',
|
|
167
|
+
className: 'has-inline-color',
|
|
168
|
+
attributes: {
|
|
169
|
+
style: 'style',
|
|
170
|
+
class: 'class',
|
|
171
|
+
},
|
|
172
|
+
/*
|
|
173
|
+
* Since this format relies on the <mark> tag, it's important to
|
|
174
|
+
* prevent the default yellow background color applied by most
|
|
175
|
+
* browsers. The solution is to detect when this format is used with a
|
|
176
|
+
* text color but no background color, and in such cases to override
|
|
177
|
+
* the default styling with a transparent background.
|
|
178
|
+
*
|
|
179
|
+
* @see https://github.com/WordPress/gutenberg/pull/35516
|
|
180
|
+
*/
|
|
181
|
+
__unstableFilterAttributeValue( key, value ) {
|
|
182
|
+
if ( key !== 'style' ) return value;
|
|
183
|
+
// We need to remove the extra spaces within the styles on mobile
|
|
184
|
+
const newValue = value.replace( / /g, '' );
|
|
185
|
+
// We should not add a background-color if it's already set
|
|
186
|
+
if ( newValue && newValue.includes( 'background-color' ) )
|
|
187
|
+
return newValue;
|
|
188
|
+
const addedCSS = [ 'background-color', transparentValue ].join( ':' );
|
|
189
|
+
// Prepend `addedCSS` to avoid a double `;;` as any the existing CSS
|
|
190
|
+
// rules will already include a `;`.
|
|
191
|
+
return newValue ? [ addedCSS, newValue ].join( ';' ) : addedCSS;
|
|
192
|
+
},
|
|
193
|
+
edit: TextColorEdit,
|
|
194
|
+
};
|
package/src/text-color/inline.js
CHANGED
|
@@ -42,7 +42,7 @@ function parseCSS( css = '' ) {
|
|
|
42
42
|
}, {} );
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function parseClassName( className = '', colorSettings ) {
|
|
45
|
+
export function parseClassName( className = '', colorSettings ) {
|
|
46
46
|
return className.split( ' ' ).reduce( ( accumulator, name ) => {
|
|
47
47
|
// `colorSlug` could contain dashes, so simply match the start and end.
|
|
48
48
|
if ( name.startsWith( 'has-' ) && name.endsWith( '-color' ) ) {
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { reject } from 'lodash';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WordPress dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useMemo } from '@wordpress/element';
|
|
11
|
+
import {
|
|
12
|
+
applyFormat,
|
|
13
|
+
removeFormat,
|
|
14
|
+
getActiveFormat,
|
|
15
|
+
} from '@wordpress/rich-text';
|
|
16
|
+
import {
|
|
17
|
+
useSetting,
|
|
18
|
+
getColorClassName,
|
|
19
|
+
getColorObjectByColorValue,
|
|
20
|
+
} from '@wordpress/block-editor';
|
|
21
|
+
import { BottomSheet, ColorSettings } from '@wordpress/components';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Internal dependencies
|
|
25
|
+
*/
|
|
26
|
+
import { textColor as settings } from './index';
|
|
27
|
+
import { transparentValue } from './index.js';
|
|
28
|
+
import { parseClassName } from './inline.js';
|
|
29
|
+
|
|
30
|
+
function parseCSS( css = '' ) {
|
|
31
|
+
return css.split( ';' ).reduce( ( accumulator, rule ) => {
|
|
32
|
+
if ( rule ) {
|
|
33
|
+
const [ property, value ] = rule.replace( / /g, '' ).split( ':' );
|
|
34
|
+
if ( property === 'color' ) accumulator.color = value;
|
|
35
|
+
if ( property === 'background-color' && value !== transparentValue )
|
|
36
|
+
accumulator.backgroundColor = value;
|
|
37
|
+
}
|
|
38
|
+
return accumulator;
|
|
39
|
+
}, {} );
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getActiveColors( value, name, colorSettings ) {
|
|
43
|
+
const activeColorFormat = getActiveFormat( value, name );
|
|
44
|
+
|
|
45
|
+
if ( ! activeColorFormat ) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...parseCSS( activeColorFormat.attributes.style ),
|
|
51
|
+
...parseClassName( activeColorFormat.attributes.class, colorSettings ),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function setColors( value, name, colorSettings, colors ) {
|
|
56
|
+
const { color, backgroundColor } = {
|
|
57
|
+
...getActiveColors( value, name, colorSettings ),
|
|
58
|
+
...colors,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if ( ! color && ! backgroundColor ) {
|
|
62
|
+
return removeFormat( value, name );
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const styles = [];
|
|
66
|
+
const classNames = [];
|
|
67
|
+
const attributes = {};
|
|
68
|
+
|
|
69
|
+
if ( backgroundColor ) {
|
|
70
|
+
styles.push( [ 'background-color', backgroundColor ].join( ':' ) );
|
|
71
|
+
} else {
|
|
72
|
+
// Override default browser color for mark element.
|
|
73
|
+
styles.push( [ 'background-color', transparentValue ].join( ':' ) );
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if ( color ) {
|
|
77
|
+
const colorObject = getColorObjectByColorValue( colorSettings, color );
|
|
78
|
+
|
|
79
|
+
if ( colorObject ) {
|
|
80
|
+
classNames.push( getColorClassName( 'color', colorObject.slug ) );
|
|
81
|
+
styles.push( [ 'color', colorObject.color ].join( ':' ) );
|
|
82
|
+
} else {
|
|
83
|
+
styles.push( [ 'color', color ].join( ':' ) );
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if ( styles.length ) attributes.style = styles.join( ';' );
|
|
88
|
+
if ( classNames.length ) attributes.class = classNames.join( ' ' );
|
|
89
|
+
|
|
90
|
+
const format = { type: name, attributes };
|
|
91
|
+
const hasNoSelection = value.start === value.end;
|
|
92
|
+
const isAtTheEnd = value.end === value.text.length;
|
|
93
|
+
const previousCharacter = value.text.charAt( value.end - 1 );
|
|
94
|
+
|
|
95
|
+
// Force formatting due to limitations in the native implementation
|
|
96
|
+
if (
|
|
97
|
+
hasNoSelection &&
|
|
98
|
+
( value.text.length === 0 ||
|
|
99
|
+
( previousCharacter === ' ' && isAtTheEnd ) )
|
|
100
|
+
) {
|
|
101
|
+
// For cases where there's no text selected, there's a space before
|
|
102
|
+
// the current caret position and it's at the end of the text.
|
|
103
|
+
return applyFormat( value, format, value.start - 1, value.end + 1 );
|
|
104
|
+
} else if ( hasNoSelection && isAtTheEnd ) {
|
|
105
|
+
// If there's no selection and is at the end of the text
|
|
106
|
+
// manually add the format within the current caret position.
|
|
107
|
+
const newFormat = applyFormat( value, format );
|
|
108
|
+
const { activeFormats } = newFormat;
|
|
109
|
+
newFormat.formats[ value.start ] = [
|
|
110
|
+
...reject( activeFormats, { type: format.type } ),
|
|
111
|
+
format,
|
|
112
|
+
];
|
|
113
|
+
return newFormat;
|
|
114
|
+
} else if ( hasNoSelection ) {
|
|
115
|
+
return removeFormat( value, format );
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return applyFormat( value, format );
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function ColorPicker( { name, value, onChange } ) {
|
|
122
|
+
const property = 'color';
|
|
123
|
+
const colors = useSetting( 'color.palette' ) || settings.colors;
|
|
124
|
+
const colorSettings = {
|
|
125
|
+
colors,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const onColorChange = useCallback(
|
|
129
|
+
( color ) => {
|
|
130
|
+
if ( color !== '' ) {
|
|
131
|
+
onChange(
|
|
132
|
+
setColors( value, name, colors, { [ property ]: color } )
|
|
133
|
+
);
|
|
134
|
+
// Remove formatting if the color was reset, there's no
|
|
135
|
+
// current selection and the previous character is a space
|
|
136
|
+
} else if (
|
|
137
|
+
value?.start === value?.end &&
|
|
138
|
+
value.text?.charAt( value?.end - 1 ) === ' '
|
|
139
|
+
) {
|
|
140
|
+
onChange(
|
|
141
|
+
removeFormat( value, name, value.end - 1, value.end )
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
onChange( removeFormat( value, name ) );
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
[ colors, onChange, property ]
|
|
148
|
+
);
|
|
149
|
+
const activeColors = useMemo(
|
|
150
|
+
() => getActiveColors( value, name, colors ),
|
|
151
|
+
[ name, value, colors ]
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<ColorSettings
|
|
156
|
+
colorValue={ activeColors[ property ] }
|
|
157
|
+
onColorChange={ onColorChange }
|
|
158
|
+
defaultSettings={ colorSettings }
|
|
159
|
+
hideNavigation
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default function InlineColorUI( { name, value, onChange, onClose } ) {
|
|
165
|
+
return (
|
|
166
|
+
<BottomSheet
|
|
167
|
+
isVisible
|
|
168
|
+
onClose={ onClose }
|
|
169
|
+
hideHeader
|
|
170
|
+
contentStyle={ { paddingLeft: 0, paddingRight: 0 } }
|
|
171
|
+
hasNavigation
|
|
172
|
+
leftButton={ null }
|
|
173
|
+
testID="inline-text-color-modal"
|
|
174
|
+
>
|
|
175
|
+
<BottomSheet.NavigationContainer animate main>
|
|
176
|
+
<BottomSheet.NavigationScreen name="text-color">
|
|
177
|
+
<ColorPicker
|
|
178
|
+
name={ name }
|
|
179
|
+
value={ value }
|
|
180
|
+
onChange={ onChange }
|
|
181
|
+
/>
|
|
182
|
+
</BottomSheet.NavigationScreen>
|
|
183
|
+
</BottomSheet.NavigationContainer>
|
|
184
|
+
</BottomSheet>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.components-inline-color--is-active {
|
|
2
|
+
border-radius: 18px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.components-inline-color__outline {
|
|
6
|
+
border-color: $light-dim;
|
|
7
|
+
top: 6px;
|
|
8
|
+
bottom: 6px;
|
|
9
|
+
left: 11px;
|
|
10
|
+
right: 11px;
|
|
11
|
+
border-radius: 24px;
|
|
12
|
+
border-width: $border-width;
|
|
13
|
+
position: absolute;
|
|
14
|
+
z-index: 2;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.components-inline-color__outline--dark {
|
|
18
|
+
border-color: $dark-ultra-dim;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.components-inline-color__button-container {
|
|
22
|
+
padding: 6px;
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`Text color allows toggling the highlight color feature to selected text 1`] = `
|
|
4
|
+
"<!-- wp:paragraph -->
|
|
5
|
+
<p><mark style=\\"background-color:rgba(0, 0, 0, 0);color:#f78da7\\" class=\\"has-inline-color has-pale-pink-color\\">Hello this is a test</mark></p>
|
|
6
|
+
<!-- /wp:paragraph -->"
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
exports[`Text color allows toggling the highlight color feature to type new text 1`] = `
|
|
10
|
+
"<!-- wp:paragraph -->
|
|
11
|
+
<p><mark style=\\"background-color:rgba(0, 0, 0, 0);color:#f78da7\\" class=\\"has-inline-color has-pale-pink-color\\"></mark></p>
|
|
12
|
+
<!-- /wp:paragraph -->"
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
exports[`Text color creates a paragraph block with the text color format 1`] = `
|
|
16
|
+
"<!-- wp:paragraph -->
|
|
17
|
+
<p>Hello <mark style=\\"background-color:rgba(0,0,0,0);color:#cf2e2e\\" class=\\"has-inline-color has-vivid-red-color\\">this is a test</mark></p>
|
|
18
|
+
<!-- /wp:paragraph -->"
|
|
19
|
+
`;
|