Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion packages/annotations/src/format/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,28 @@ export const annotation = {
};
},
__experimentalCreatePrepareEditableTree( { annotations } ) {
return ( formats, text ) => {
return ( formats, text, value ) => {
if ( annotations.length === 0 ) {
return formats;
}

let record = { formats, text };
// Preserve activeFormats when applying annotations so user formats
// (like bold) are not lost when annotations are added programmatically.
const originalActiveFormats = value?.activeFormats
? value.activeFormats.filter(
( format ) => format.type !== FORMAT_NAME
)
: undefined;
if ( originalActiveFormats ) {
record.activeFormats = originalActiveFormats;
}
record = applyAnnotations( record, annotations );
// Restore original activeFormats after applying annotations, since
// annotations are editor-only formats and shouldn't be in activeFormats.
if ( originalActiveFormats ) {
record.activeFormats = originalActiveFormats;
}
return record.formats;
};
},
Expand Down
2 changes: 1 addition & 1 deletion packages/block-editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ export function RichTextWrapper(

function addInvisibleFormats( value ) {
return prepareHandlers.reduce(
( accumulator, fn ) => fn( accumulator, value.text ),
( accumulator, fn ) => fn( accumulator, value.text, value ),
value.formats
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,18 @@ export default ( props ) => ( element ) => {
const currentValue = createRecord();
const { start, activeFormats: oldActiveFormats = [] } = record.current;

// Filter out editor-only formats (like annotations) from activeFormats,
// since they shouldn't be applied when typing.
const filteredActiveFormats = oldActiveFormats.filter(
( format ) => format.type !== 'core/annotation'
);

// Update the formats between the last and new caret position.
const change = updateFormats( {
value: currentValue,
start,
end: currentValue.start,
formats: oldActiveFormats,
formats: filteredActiveFormats,
} );

handleChange( change );
Expand Down Expand Up @@ -169,6 +175,7 @@ export default ( props ) => ( element ) => {
);

// Update the value with the new active formats.
// getActiveFormats already filters out editor-only formats like annotations.
newValue.activeFormats = newActiveFormats;

// It is important that the internal value is updated first,
Expand Down
20 changes: 20 additions & 0 deletions packages/rich-text/src/component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ export function useRichText( {
function applyFromProps() {
// Get previous value before updating
const previousValue = _valueRef.current;
// Preserve activeFormats from current record before updating
const preservedActiveFormats = recordRef.current?.activeFormats
? recordRef.current.activeFormats.filter(
( format ) => format.type !== 'core/annotation'
)
: undefined;

setRecordFromProps();

Expand All @@ -168,6 +174,20 @@ export function useRichText( {
ref.current.ownerDocument.activeElement
);

// Preserve activeFormats when only formats changed (e.g., annotations added),
// not when content length changed or element doesn't have focus.
if (
preservedActiveFormats &&
! contentLengthChanged &&
hasFocus &&
recordRef.current.start === recordRef.current.end
) {
recordRef.current = {
...recordRef.current,
activeFormats: preservedActiveFormats,
};
}

// Skip re-applying the selection state when content changed from external source
// (e.g., typing in sidebar input changes canvas text)
const skipSelection = contentLengthChanged && ! hasFocus;
Expand Down
37 changes: 30 additions & 7 deletions packages/rich-text/src/get-active-formats.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,31 @@ export function getActiveFormats( value, EMPTY_ACTIVE_FORMATS = [] ) {
if ( start === end ) {
// For a collapsed caret, it is possible to override the active formats.
if ( activeFormats ) {
return activeFormats;
// Filter out editor-only formats (like annotations) from activeFormats.
return activeFormats.filter(
( format ) => format.type !== 'core/annotation'
);
}

const formatsBefore = formats[ start - 1 ] || EMPTY_ACTIVE_FORMATS;
const formatsAfter = formats[ start ] || EMPTY_ACTIVE_FORMATS;

// Filter out editor-only formats (like annotations) when calculating from formats.
const filteredFormatsBefore = formatsBefore.filter(
( format ) => format.type !== 'core/annotation'
);
const filteredFormatsAfter = formatsAfter.filter(
( format ) => format.type !== 'core/annotation'
);

// By default, select the lowest amount of formats possible (which means
// the caret is positioned outside the format boundary). The user can
// then use arrow keys to define `activeFormats`.
if ( formatsBefore.length < formatsAfter.length ) {
return formatsBefore;
if ( filteredFormatsBefore.length < filteredFormatsAfter.length ) {
return filteredFormatsBefore;
}

return formatsAfter;
return filteredFormatsAfter;
}

// If there's no formats at the start index, there are not active formats.
Expand All @@ -47,9 +58,8 @@ export function getActiveFormats( value, EMPTY_ACTIVE_FORMATS = [] ) {

const selectedFormats = formats.slice( start, end );

// Clone the formats so we're not mutating the live value.
const _activeFormats = [ ...selectedFormats[ 0 ] ];
let i = selectedFormats.length;
let _activeFormats;

// For performance reasons, start from the end where it's much quicker to
// realise that there are no active formats.
Expand All @@ -62,6 +72,19 @@ export function getActiveFormats( value, EMPTY_ACTIVE_FORMATS = [] ) {
return EMPTY_ACTIVE_FORMATS;
}

// Filter out editor-only formats (like annotations) from formats at this index.
const filteredFormatsAtIndex = formatsAtIndex.filter(
( format ) => format.type !== 'core/annotation'
);

// Clone the formats so we're not mutating the live value.
// Filter out editor-only formats (like annotations) from the start.
// Assign only when we know we'll use it (after early return check).
if ( _activeFormats === undefined ) {
_activeFormats = ( selectedFormats[ 0 ] || [] ).filter(
( format ) => format.type !== 'core/annotation'
);
}
let ii = _activeFormats.length;

// Loop over the active formats and remove any that are not present at
Expand All @@ -70,7 +93,7 @@ export function getActiveFormats( value, EMPTY_ACTIVE_FORMATS = [] ) {
const format = _activeFormats[ ii ];

if (
! formatsAtIndex.find( ( _format ) =>
! filteredFormatsAtIndex.find( ( _format ) =>
isFormatEqual( format, _format )
)
) {
Expand Down
Loading