gutenberg icon indicating copy to clipboard operation
gutenberg copied to clipboard

Site Editor: Tab order of editor canvas and resize handle

Open mirka opened this issue 2 years ago • 6 comments

In #52443, it was raised that the tab order of the editor canvas and resize handle was unintuitive:

One thing I found a bit confusing is that the 'Drag to resize' handle is placed in the tab sequence after the iframe. I would expect to find it between the navigation and the iframe so that it also matches the visual order.

It would be nice if we could flip the tab order. However, it isn't very easy because the element order depends on the re-resizable library under the hood.

Also, in the general case, resize handles can be on any edge or corner. It's only in this specific case where the handle is only on the left edge. So I do see an argument to optimize for consistency in the general case (for example the one in Style Book with two handles), and have all handles come after the frame.

mirka avatar Jul 13 '23 13:07 mirka

To my understanding, the problem with the ResizableBox component and the re-resizable library is that it is meant for a different use case :)

By default, re-resizable draws a rectangular box with 8 handles: 4 for the corners and 4 for the edges. Screenshot:

Screenshot 2023-07-18 at 14 11 15

To do so, re-resizable draws an overlay above the element that is resizable and in the DOM order the overlay comes after.

It is possible to hide or disable the handles and keep just one or two of them but regardless they will always come after in the DOM order. I'm not sure there is a way to hack around the re-resizable library but anyways I'm not sure hacking it would be future-proof.

I tend to think the ResizableBox component based on re-resizable is just not the right tool for our use case.

Worth reminding that the DOM order problem occurs also in the Style Book where there is an additional problem:

  • Both handles come after the resizable content.
  • The right handle is placed in the DOM before the left one so the Tab order is from right to left, which is a totally unexpected user experience.

Marking this issue as a bug because the mismatch between visual order and DOM order is a WCAG violation.

afercia avatar Jul 18 '23 12:07 afercia

Looking into this after some time. I'd think the best way to solve this issue would be:

  • either submit a change request upstream
  • or fork re-resizable and build our own, so that we can change its render method.

Looking at re-resizable code:

https://github.com/bokuweb/re-resizable/blob/0f6b2ddcbab2a2864c209e0a59a67b6d51adab86/src/index.tsx#L949-L955

    return (
      <Wrapper ref={this.ref} style={style} className={this.props.className} {...extendsProps}>
        {this.state.isResizing && <div style={this.state.backgroundStyle} />}
        {this.props.children}
        {this.renderResizer()}
      </Wrapper>
    );
  • renderResizer renders the resizer overlay, which contains the resize handles.
  • The resizer is always rendered after the resizable element (the children passd via props).
  • Instead, what we need is;
    • An option to render the resizer before.
    • Even better: a way to render each resize handle before or after the resizable element.

afercia avatar Jan 02 '24 11:01 afercia

After https://github.com/WordPress/gutenberg/pull/65637 it would be good to look again into this issue.

afercia avatar Sep 27 '24 12:09 afercia

It seems that https://github.com/bokuweb/re-resizable/pull/827, which was submitted to the re-resizable library to solve this problem, caused a regression 😅 Therefore, the PR was reverted.

I can think of three approaches:

  • Resubmit a different PR to the re-resizable library that does not cause a regression.
  • Create a new component that does not depend on external libraries, such as ResizableBoxV2.
  • Wait for the new reading-flow css to be implemented in browsers. This is not yet implemented in major browsers, but it may be the simplest approach.

t-hamano avatar Nov 06 '24 07:11 t-hamano

  • Create a new component that does not depend on external libraries, such as ResizableBoxV2.

I’ll add that re-resizable is ~50kB. Ideally we could manage to do this with a backwards compatible API and not have to export a new component while simultaneously reducing the final bundled size because we could remove that dependency. For the dragging behavior, we could utilize @use-gesture/react which is already bundled in the components package. From there it’s mostly a matter of implementing the UI around that. #66735 happens to serve as an example of how (at least in that use case) the current API/implementation of ResizableBox doesn’t seem to simplify the implementation of a keyboard focusable resizable pane.

  • Wait for the new reading-flow css to be implemented in browsers. This is not yet implemented in major browsers, but it may be the simplest approach.

Thanks for sharing the link, I hadn’t seen that and it would be handy.

stokesman avatar Nov 06 '24 16:11 stokesman

Wait for the new reading-flow css to be implemented in browsers. This is not yet implemented in major browsers, but it may be the simplest approach.

Chrome 137 supports a and b, but unfortunately this may not be applicable to all UIs.

As I understand it, these CSS properties are for changing the logical order of child elements. However, the elements (children, each resize handle) in the ResizableBox component are not parallel.

The approach using reading-flow and reading-order will work correctly when there is only one resize handle, such as the initial canvas of the site editor:

<div class="components-resizable-box__container edit-site-resizable-frame__inner">
  <!-- Change the tab order to second -->
  <div class="edit-site-resizable-frame__inner-content" style="reading-order: 2;">
    <div class="editor-editor-interface edit-site-editor__editor-interface">
      <div class="interface-interface-skeleton__editor">
        <div class="interface-interface-skeleton__body">
          <div class="interface-navigable-region interface-interface-skeleton__content" aria-label="Editor content" role="region" tabindex="-1"></div>
        </div>
      </div>
    </div>
  </div>
  <!-- Change the tab order to first -->
  <div style="reading-order: 1;">
    <div class="">
      <button class="edit-site-resizable-frame__handle" aria-label="Drag to resize"></button>
    </div>
  </div>
</div>

However, it does not solve the problem when there are multiple resize handles, such as the pattern editor and template part editor. This is because the resize handles are placed in nested div elements and are not parallel to the editor canvas:

<div class="components-resizable-box__container has-show-handle editor-resizable-editor">
  <!-- We want the tab order to be second -->
  <div style="height: 100%; display: flex;">
    <div class="block-editor-iframe__container">
      <div class="block-editor-iframe__scale-container">
        <div tabindex="0"></div>
        <iframe class="edit-site-visual-editor__editor-canvas" name="editor-canvas" tabindex="0"></iframe>
        <div tabindex="0"></div>
      </div>
    </div>
  </div>
  <div>
    <!-- We want the tab order to be first -->
    <div class="">
      <button class="editor-resizable-editor__resize-handle is-left" aria-label="Drag to resize" tabindex="0"></button>
    </div>
    <!-- We want the tab order to be third -->
    <div class="">
      <button class="editor-resizable-editor__resize-handle is-right" aria-label="Drag to resize" tabindex="0"></button>
    </div>
  </div>
</div>

t-hamano avatar Jun 03 '25 04:06 t-hamano