Repository: wagtail/wagtail
File: client/src/components/CommentApp/components/Comment/index.tsx (TypeScript)

Code before:
    };

    const onDoubleClick = () => {
      this.props.store.dispatch(
        setFocusedComment(this.props.comment.localId, {
          updatePinnedComment: true,
          forceFocus: true,
        }),
      );
    };

    const top = this.props.layout.getCommentPosition(
      this.props.comment.localId,
    );

    return (
      <FocusTrap
        focusTrapOptions={
          {
            preventScroll: true,
            clickOutsideDeactivates: true,
            onDeactivate: () => {
              this.props.store.dispatch(
                setFocusedComment(null, {
                  updatePinnedComment: true,
                  forceFocus: false,
                }),
              );
            },
            // Allow delay for side panel to open and comment card to fade in with animations
            checkCanFocusTrap: () =>
              new Promise((resolve) => {
                setTimeout(resolve, 250);
              }),
            initialFocus: '[data-focus-target="true"]',
            delayFocus: false,
          } as any
        } // For some reason, the types for FocusTrap props don't yet include preventScroll.
        active={this.props.isFocused && this.props.forceFocus}
      >
        <li
          tabIndex={-1}
          data-focus-target={
            this.props.isFocused &&
            !['creating', 'editing'].includes(this.props.comment.mode)
          }
          key={this.props.comment.localId}
          className={`comment comment--mode-${this.props.comment.mode} ${
            this.props.isFocused ? 'comment--focused' : ''
          }`}
          style={{

Review comment (suggestion):
I think we should first check if the element's available, just in case we can avoid the delay.

I have not run this code, but here's a rough start.

```suggestion
            // Allow delay for side panel to open and comment card to fade in with animations
            checkCanFocusTrap: ({ containers = [] }) => {
              if (containers.some((container) => container.querySelector('[data-focus-target="true"]'))) {
                return Promise.resolve();
              }
              return new Promise((resolve) => {
                setTimeout(resolve, 250);
              })
            },
```


> checkCanFocusTrap {(containers: Array<HTMLElement | SVGElement>) => Promise<void>}: Animated dialogs have a small delay between when onActivate is called and when the focus trap is focusable. checkCanFocusTrap expects a promise to be returned. When that promise settles (resolves or rejects), focus will be sent to the first tabbable node (in tab order) in the focus trap (or the node configured in the initialFocus option).



Code after:
    };

    const onDoubleClick = () => {
      this.props.store.dispatch(
        setFocusedComment(this.props.comment.localId, {
          updatePinnedComment: true,
          forceFocus: true,
        }),
      );
    };

    const top = this.props.layout.getCommentPosition(
      this.props.comment.localId,
    );

    return (
      <FocusTrap
        focusTrapOptions={{
          preventScroll: true,
          clickOutsideDeactivates: true,
          onDeactivate: () => {
            this.props.store.dispatch(
              setFocusedComment(null, {
                updatePinnedComment: true,
                forceFocus: false,
              }),
            );
          },
          /** Allow delay for side panel to open and comment card to fade in with animations. */
          checkCanFocusTrap: (containers: (HTMLElement | SVGElement)[]) => {
            const hasFocusTargetWithSidePanelOpen = containers.some(
              (container) =>
                container.matches(
                  '[data-form-side="comments"].form-side--comments [data-focus-target="true"]',
                ),
            );

            if (hasFocusTargetWithSidePanelOpen) return Promise.resolve();

            return new Promise((resolve) => {
              setTimeout(resolve, 250);
            });
          },
          initialFocus: '[data-focus-target="true"]',
          delayInitialFocus: true,
        }}
        active={this.props.isFocused && this.props.forceFocus}
      >
        <li
          tabIndex={-1}
          data-focus-target={