Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RichText/Input Interaction: use ZWNBSP as padding #14846

Merged
merged 6 commits into from
Jul 26, 2019
Merged

Conversation

ellatrix
Copy link
Member

@ellatrix ellatrix commented Apr 5, 2019

Description

<br> elements as padding for rich text elements proved to be no good.

Screenshot 2019-07-24 at 17 30 02

/**
* Get the rectangle of a given Range.
*
* @param {Range} range The range.
*
* @return {DOMRect} The rectangle.
*/
export function getRectangleFromRange( range ) {
// For uncollapsed ranges, get the rectangle that bounds the contents of the
// range; this a rectangle enclosing the union of the bounding rectangles
// for all the elements in the range.
if ( ! range.collapsed ) {
return range.getBoundingClientRect();
}
const { startContainer } = range;
// Correct invalid "BR" ranges. The cannot contain any children.
if ( startContainer.nodeName === 'BR' ) {
const { parentNode } = startContainer;
const index = Array.from( parentNode.childNodes ).indexOf( startContainer );
range = document.createRange();
range.setStart( parentNode, index );
range.setEnd( parentNode, index );
}
let rect = range.getClientRects()[ 0 ];
// If the collapsed range starts (and therefore ends) at an element node,
// `getClientRects` can be empty in some browsers. This can be resolved
// by adding a temporary text node with zero-width space to the range.
//
// See: https://stackoverflow.com/a/6847328/995445
if ( ! rect ) {
const padNode = document.createTextNode( '\u200b' );
range.insertNode( padNode );
rect = range.getClientRects()[ 0 ];
padNode.parentNode.removeChild( padNode );
}
return rect;
}

If we use zero width spaces as padding, the calculation is more reliable, and Firefox doesn't have any issues.

It also fixes an issue in the list block, where to browser moves the selection from an empty text element between a br and nested list to the start of the nested list.

How has this been tested?

Screenshots

Types of changes

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.

@ellatrix ellatrix added [Feature] Writing Flow Block selection, navigation, splitting, merging, deletion... [Package] Rich text /packages/rich-text labels Apr 5, 2019
@ellatrix ellatrix force-pushed the try/zwnbsp-padding branch 2 times, most recently from eac10df to 8c2699b Compare April 24, 2019 11:52
@ellatrix ellatrix changed the title RichText/Input Interaction: try ZWNBSP as padding RichText/Input Interaction: use ZWNBSP as padding Apr 24, 2019
Copy link
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build failure appears to be legitimate. I can also reproduce locally that when pressing ArrowDown from a bottom-padded paragraph, the caret is not placed in the text field (though the <p contenteditable> does become the active element).

@ellatrix
Copy link
Member Author

@aduth Yeah, I know, I haven't had the chance to look at it yet.

@ellatrix ellatrix added the [Status] In Progress Tracking issues with work in progress label Apr 24, 2019
@ellatrix ellatrix force-pushed the try/zwnbsp-padding branch from 8c2699b to 9be2f6f Compare May 14, 2019 12:04
@ellatrix ellatrix mentioned this pull request May 14, 2019
5 tasks
@ellatrix
Copy link
Member Author

I can't reproduce the error locally. :/

@aduth
Copy link
Member

aduth commented May 14, 2019

Testing again, I still observe issues, though in my case it works for the ArrowDown from the padded field, but the caret becomes lost when pressing ArrowUp to try to get back to the first field.

I created a screen recording in case it helps provide any useful context: https://cloudup.com/crRofrsTvMJ

@aduth
Copy link
Member

aduth commented May 14, 2019

In debugging, I notice that in placeCaretAtVerticalEdge, it computes a new range using hiddenCaretRangeFromPoint, where the range object produced by this function is anchored to a button in the block contextual toolbar:

image

Unclear if this might explain the issue (it's trying to move the selection into the hidden toolbar button?). I'm assuming the caretRangeFromPoint might be misidentifying some hidden elements.

If you're having trouble reproducing, it may be that you're configured to use the Fixed toolbar?

@ellatrix
Copy link
Member Author

@aduth I don't see a toolbar in the video you shared. Not sure how it would catch the selection...

@ellatrix ellatrix requested a review from SergioEstevao as a code owner July 24, 2019 14:37
@@ -407,20 +407,6 @@ export function placeCaretAtVerticalEdge( container, isReverse, rect, mayUseScro
return;
}

// Check if the closest text node is actually further away.
// If so, attempt to get the range again with the y position adjusted to get the right offset.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea what this code is for. Apparently I wrote it! :) Since we're moving from a br element to a text element for an empty rich text, this calculation makes should navigate contenteditable with padding fail.

Since I don't understand what this is for, and there are no failing e2e tests when removing the code, I think it's safe to remove. If we end up needing it again, we should comment it better and add e2e tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aduth This appeared to be the culprit of the e2e test failure.

@ellatrix ellatrix removed the [Status] In Progress Tracking issues with work in progress label Jul 24, 2019
@ellatrix ellatrix added this to the Gutenberg 6.2 milestone Jul 24, 2019
@ellatrix ellatrix requested a review from aduth July 24, 2019 15:42
@@ -3,3 +3,4 @@
*/
export const LINE_SEPARATOR = '\u2028';
export const OBJECT_REPLACEMENT_CHARACTER = '\ufffc';
export const ZWNBSP = '\ufeff';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could help to have some documentation about what these special characters are intended to be used for.

@ellatrix ellatrix merged commit bf8e942 into master Jul 26, 2019
@ellatrix ellatrix deleted the try/zwnbsp-padding branch July 26, 2019 09:58
@ellatrix
Copy link
Member Author

Thanks @aduth!

@dmsnell
Copy link
Member

dmsnell commented Sep 15, 2021

@ellatrix thanks for making this change that improved things with the editor and list and all that. I'm coming late to the part but have a question about the character being used. It seems that the use of U+FEFF for zero-width space or zero-width joining was deprecated in 2002 in TR28, being replaced by the favored ZWJ. This was because the code point was in use both for the byte-order-mark (BOM) and also the joiner.

Was U+200B (zero-width space) or even U+200C (zero-width non-joiner) considered for this role? Or any other characters? I think it could be worth replacing this character with another that doesn't involve the confusion.

@ellatrix
Copy link
Member Author

@dmsnell

The problem with the zero-width space is that it could have some usage in text. The zero-width non-joiner also seems to be used for typography.

The zero-width non breaking space is taken over from TinyMCE. The fact that it's deprecated is actually a good thing. That means it will doesn't have any real usage in text anymore, so it's won't clash with anything.

I'm always open to alternatives. Maybe we can use one of the control characters. But if there's no real problem with using it, I'm not sure if we could replace it.

@dmsnell
Copy link
Member

dmsnell commented Sep 22, 2021

The fact that it's deprecated is actually a good thing. That means it will doesn't have any real usage in text anymore, so it's won't clash with anything.

The character isn't deprecated, just its use as we are using it, or as TinyMCE did. It's still used as a byte-order mark in other places.

Maybe we can use one of the control characters

Did we consider some private-use characters or field separators? I'm guessing this is used to delineate formatted regions? Does the character have to have a visual representation that's invisible and zero-width?

We have U+1D, for example, the "group separator" and it seems to display invisibly (without a replacement character). Some of these old control codes I've never seen in practice despite having fairly semantically-related meanings.

@ellatrix
Copy link
Member Author

The character isn't deprecated, just its use as we are using it, or as TinyMCE did. It's still used as a byte-order mark in other places.

Ok, the use as a space character is deprecated, which makes it pretty much useless in text for users. If it was still used as a space character, then we wouldn't be using it.

Did we consider some private-use characters or field separators?

That's what I mean with control characters. I'm just wondering why ZWNBSP is problematic.

Does the character have to have a visual representation that's invisible and zero-width?

Yes, it has to be zero width. U+1D seems to be fine for that.

@dmsnell
Copy link
Member

dmsnell commented Sep 22, 2021

I'm just wondering why ZWNBSP is problematic.

It stood out to me when I was debugging the other issue where they were bleeding out of RichText. I'm not entirely sure of this, but I think the main problem (besides the fact that ZWNBSP as a character "doesn't exist" anymore - it does of course, but shouldn't be used) is that we don't expect to find the BOM in the middle of a text stream (it should come as the first unit in the stream).

To my knowledge nothing is broken because of the use of this character; it's more that I came across it and was confused at its use, and it distracted me while trying to fix the matter at hand. My question was focused on (a) did we have an intentional reason to pick the BOM or did we choose it because it was invisible and rare, and (b) if the first part is "no" then I wonder if there aren't characters more closely designated for the purposes we're using them for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Writing Flow Block selection, navigation, splitting, merging, deletion... [Package] Rich text /packages/rich-text
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants