qwik icon indicating copy to clipboard operation
qwik copied to clipboard

[๐Ÿž] SPA routing is preventing browser defaults

Open tzdesign opened this issue 2 years ago โ€ข 3 comments

Which component is affected?

Qwik Runtime

Describe the bug

When you press cmd + click on a link, qwik uses preventDefault.

The default browser experience would open a new tab. If you add shift to the party, it'll even open it directly. I know there are several other shortcuts to open in a new window or private window.

Qwik should not prevent that kind of functionality.

Reproduction

https://stackblitz.com/edit/qwik-starter-qads2x?file=src%2Froutes%2Findex.tsx

Steps to reproduce

Run it on stackblitz.

Click flower app with cmd

You just get routed

Click todo app with cmd

You get to a new tab

System Info

See stackblitz

Additional Information

No response

tzdesign avatar Apr 21 '23 09:04 tzdesign

is it a thing in other metaframeworks like nextjs, when using Link? sounds more like a feature request than a bug

manucorporat avatar Apr 21 '23 10:04 manucorporat

@manucorporat no I think you are preventing default. In react-router and next it's only preventing default if shiftKey, meta and ctrl are not true.

I checked it with our internal apps and my personal nextjs page.

We discussed this with @hamatoyogi and @shairez, and I think we agreed that qwik should avoid preventing browser APIs as much as possible.

You can call it a feature request, but I would say it should have been implemented in the same way already.

tzdesign avatar Apr 24 '23 09:04 tzdesign

@jordanw66 I saw you changes on the link component #4558

The current component completely suppresses the default from the browser. Examples:

  • Meta + Click / Ctrl + Click ยป Open new Tab
  • Alt + Click ยป Download resource
  • Meta + Shift + Click ยป Open Tab in foreground

To get this to work, the only thing I can think of is to toggle preventDefault in the event. But as it's async it's impossible to get it to work.

Do you have any idea, if this is even possible? @manucorporat said there is currently no way to dynamically preventDefault conditionally.

I was thinking of smt like this, but nativeEvent is empty:

const isClientNavigation =
        (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) === false &&
        Boolean(clientNavPath);
      if (isClientNavigation === false) {
        // Do not enter the nav pipeline if this is not a clientNavPath or the user hit one of the modifier keys.
        return;
      }
      event.nativeEvent?.preventDefault();

tzdesign avatar Jun 27 '23 09:06 tzdesign

@jordanw66 I agree, trying to fake the default behavior is not an option.

I think that qwik suppresses the default behavior here is a mistake. However, I don't know how important it is to everyone to go to the qwik-loader here.

Do you think it's justified to pursue it here? I personally use meta + shift + click every single day.

@manucorporat what is your opinion about it?

tzdesign avatar Jun 27 '23 09:06 tzdesign

@jordanw66 perfect idea. Your suggestion is awesome. The question is, how deep do we want to go. I might want to implement it for all developers and pass the allowmodifiers as a type like that:

export type AllowModifiers = SingleOrArray<
  keyof Pick<QwikMouseEvent, 'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey'>
>;

This way it's connected to the event and the developer can be specific. For link we set linkProps to all of them.

I have some space tomorrow and can check it out, but maybe @manucorporat can tell us if this is ok. We also can catch up and talk about it, as Manu wanted to write e2e tests for navigation?

tzdesign avatar Jun 27 '23 11:06 tzdesign

is it a thing in other metaframeworks like nextjs, when using Link? sounds more like a feature request than a bug

https://github.com/vercel/next.js/blob/e33b87d894a8e80a4328adf1fd3d472896935be0/packages/next/src/client/link.tsx#L208

function isModifiedEvent(event: React.MouseEvent): boolean {
  const eventTarget = event.currentTarget as HTMLAnchorElement | SVGAElement
  const target = eventTarget.getAttribute('target')
  return (
    (target && target !== '_self') ||
    event.metaKey ||
    event.ctrlKey ||
    event.shiftKey ||
    event.altKey || // triggers resource download
    (event.nativeEvent && event.nativeEvent.which === 2)
  )
}

@manucorporat here is how next is doing it, but of course they are only on the client in this case.

tzdesign avatar Jun 28 '23 13:06 tzdesign

This is solved by sync$()

gioboa avatar Jan 24 '24 21:01 gioboa

@gioboa @mhevery just perfect ๐Ÿฅน

The sync$ is also a pretty awesome new feature

tzdesign avatar Jan 27 '24 11:01 tzdesign

Nice, I'm glad to hear that ๐Ÿ˜‰

gioboa avatar Jan 27 '24 12:01 gioboa