theKashey / react-focus-on

🎯 Solution for WAI ARIA compatible modal dialogs or full-screen tasks, you were looking for
MIT License
340 stars 16 forks source link

onClickOutside is never triggered with React portals #44

Closed mike-lischke closed 4 years ago

mike-lischke commented 4 years ago

I'm currently experimenting with react-focus-on in a project of mine to see if that works as I need it. So far, it did what I expected, but I cannot get one thing to work: onClickOutside is never triggered for me, so I cannot automatically close popups. I'm testing on Firefox mostly, but I guess this doesn't play a role here.

My portal render code is this:

public render(): React.ReactNode {
    const { children, container = document.body } = this.props;
    const { open, options } = this.state;

    const className = this.getEffectiveClassNames([
        "portal",
    ]);

    const element = <div ref={this.contentRef}>
        {children}
    </div>;

    return (
        open && createPortal(
            <FocusOn
                shards={[this.contentRef]}
                className={className}
                enabled={true}
                scrollLock={true}
                focusLock={true}
                autoFocus={true}
                returnFocus={true}
                onClickOutside={this.handlClickOutside}
                onEscapeKey={this.handleEscapeKey}
            >
                {element}
            </FocusOn>, container,
        )
    );
}

First I rendered the children directly in the portal element, then I tried to wrap it with a div and use the ref of that for the shards property. But that didn't change anything. I'm assuming here that shards plays a role in this misfunction, but I could be completely wrong.

Can you please tell me how this should be implemented actually? The documentation is pretty sparse overall. Thanks.

theKashey commented 4 years ago

shards are used to refer some nodes except of the own children. And only children are "safe" to have a portaled content inside them. In your case, a whole FocusOn is inside a portal and everything should be ok.

I am not completely sure why onclick is not working for you, but you can try to figure out it by yourself via editing this package manually in node_modules - the file would be located at /node_modules/react-focus-on/dist/es2015/Effect.tsx https://github.com/theKashey/react-focus-on/blob/master/src/Effect.tsx#L46

      const onMouseDown = (event: MouseEvent | TouchEvent) => {
        if (
          event.defaultPrevented ||
          event.target === lastEventTarget.current ||
          (event instanceof MouseEvent && event.button !== 0)
        ) {
          // it could exit only here(if you remove shards)
          //the event is from own children or button is not "left"
          return;
        }
mike-lischke commented 4 years ago

Great to get help so quickly! As it turned out I have to place FocusOn within the portal, because the portal element is used as the (full screen) background too. If that's a child of FocusOn then there will never be a place outside of it. The working solution is now:

    public render(): React.ReactNode {
        const { children, container = document.body } = this.props;
        const { open, options } = this.state;

        return (
            open && createPortal(
                <div
                    style={{ "--background-opacity": options.backgroundOpacity }}
                >
                    <FocusOn
                        enabled={true}
                        scrollLock={true}
                        focusLock={true}
                        autoFocus={true}
                        returnFocus={true}
                        onClickOutside={this.handlClickOutside}
                        onEscapeKey={this.handleEscapeKey}
                        onActivation={this.handleActivation}
                        onDeactivation={this.handleDeactivation}
                    >
                        {children}
                    </FocusOn>
                </div>, container,
            )
        );
    }

CSS:

.portal {
    --background-opacity: 0.5;

    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;

    background: rgba(0, 0, 0, var(--background-opacity));
}

Maybe you want to put this into your readme to make it clearer how to work with portals?

theKashey commented 4 years ago

The problem is not bound to portals, but to your particular markup. You can cause the same without any portal involved :)