solid-primitives icon indicating copy to clipboard operation
solid-primitives copied to clipboard

createShortcut but allow any permutation

Open onx2 opened this issue 1 year ago • 2 comments
trafficstars

Describe The Problem To Be Solved

This may not be a bug, and if not I think it would be a nice thing to add as another function or option.

I want to use createShortcut(["Shift", "Control", "M"], () => {}) and have it trigger the function on any permutation of key presses, so how vscode does it with their shortcuts.

Suggest A Solution

I have a rough implementation of it using useKeyDownList, so something like this but maybe with also a length check?

const keys = useKeyDownList();
const myShortcut = new Set(["SHIFT", "CONTROL", "M"]);

createEffect(() => {
  const keyList = keys();
    for (const key of keyList) {
      if (!myShortcut.has(key)) return;
    }

  someCallback();
});

onx2 avatar Jul 23 '24 23:07 onx2

What is your use case for that? I’m not entirely opposed to the idea, but it might be a specific need for your app, and like you see is not hard to figure out that functionality yourself.

thetarnav avatar Jul 24 '24 06:07 thetarnav

I'm creating a keyboard shortcut manager for an application and want to allow a user to define keys bound to an action. Normally when triggering keybindings in software, for example VSCode, Notion, and many others, I can press at least the Modifier Keys in any order then a Character Key to trigger an action. You can see an example of this in VSCode with Shift + CMD + P or in Notion with Shift + CMD + L. I actually expected the default behavior of createShortcut to do this as it is very commonplace, at least in the software / apps I use. I'm happy to contribute to this as well!

Edit: As you mentioned above, this isn't difficult to create using the existing functions but I will admit I was a bit confused when createShortcut didn't have this behavior. If you think that it is best to keep it out I can close this and implement a custom function in my app instead - thanks for your time!

onx2 avatar Jul 24 '24 14:07 onx2

Better that it doesn't do this by default, because it stays true to the principle of a primitive.

If you wanted it to accept any modifier in any order: Could add an option or a different function which when passed the KBD keys, extracts modifiers into a set. The same is done for events, and then the main functionality of simply comparing the input keyset to the event keyset remains (except now we compare the sequence of character KBDs AND a set of modifier KBDs, instead of a sequence only).

I don't know javascript so I can't contribute this, but I assume it's fairly trivial.

shayanhabibi avatar Jun 12 '25 03:06 shayanhabibi

Better that it doesn't do this by default, because it stays true to the principle of a primitive.

I fully support a commitment to the principle of primitives. However, I'm concerned that the current createShortcut implementation, specifically its enforcement of key order, might unintentionally work against this.

While a shortcut utility naturally incorporates certain opinions based on an application's design and UX needs, I believe that enforcing a rigid key order at this foundational level is counter-productive. It can limit both the accessibility for users, as well as the extensibility for developers. To maintain true primitive behavior, I think it would be beneficial to reconsider whether createShortcut should enforce this order, or perhaps if it would be better to remove the utility entirely.

Like @thetarnav mentioned, its not hard to create a utility for this using the other primitives. Because of that, this seems like an abstraction that really isn't a primitive but rather more of an example of how one might use the primitives to create application-specific behavior.

onx2 avatar Jun 28 '25 12:06 onx2

We could still provide an additional wrapper if we find more applications that have the same use case. I wouldn't want to increase the bundle size of existing apps because of that, though.

atk avatar Jun 29 '25 06:06 atk

I see the point regarding whether the current implementation is overly opinionated.

With regards to this, it should be trivial to remove order on modifiers from what I saw in the source. I prefer against having other keystrokes being unordered because it simplifies the implementation, but more importantly reduces cases where we will be optimistically preventing those keystrokes. That sounds reasonable right?

But then, how would this be reflected in the API? If someone was the input a shortcut code of ctrl S alt X, would it be reasonable to expect order enforcement there?

I get the argument against ordering for modifiers, but would that only apply to any sequence of modifiers in the head of the sequence? Would it apply to any consecutive sequences of modifiers?

shayanhabibi avatar Jun 30 '25 01:06 shayanhabibi

But then, how would this be reflected in the API? If someone was the input a shortcut code of ctrl S alt X, would it be reasonable to expect order enforcement there? I get the argument against ordering for modifiers, but would that only apply to any sequence of modifiers in the head of the sequence? Would it apply to any consecutive sequences of modifiers?

These kinds of questions make me think a shortcut utility doesn't belong as a primitive, but exposing some examples of how it could work is reasonable.

Addressing Your Questions About Order Enforcement My concern isn't just about the order of modifiers, but about any implicit assumptions of order within a createShortcut primitive itself.

Regarding ctrl S alt X and order enforcement If a primitive createShortcut did exist and allowed for such complex sequences, my view is that enforcing order even there would be overly opinionated for a primitive. The moment we start debating whether alt S ctrl X should trigger ctrl S alt X, we're already entering into application-specific logic/preferences. A primitive should ideally provide the most granular building blocks possible without dictating their specific arrangement or interpretation in complex scenarios. If an application needs to differentiate between ctrl S alt X and alt S ctrl X, that's a valid design choice for that application, but it shouldn't be baked into a low-level primitive.

Regarding the scope of unordered modifiers: If we were to keep createShortcut and only relax modifier order, I'd argue it should apply to any set of modifiers concurrently together. This means that for a shortcut like Ctrl + Shift + S, pressing Shift + Ctrl + S should yield the same result.

However, even this "simple" relaxation quickly introduces complexity when we consider sequences.

To illustrate why these kinds of nuances are better handled by utilities built on top of primitives or exposed as examples, rather than as part of the primitive itself, let's consider to your point: if we have a shortcut A + Ctrl + B, does pressing Ctrl + A + B still trigger it? What about A + B + Ctrl? The distinction between concurrent key presses and sequential key presses becomes crucial, and how a primitive would implicitly handle such variations adds significant opinion.

Zed, for example, allows pressing cmd + s to trigger "save," but if you hold those down and then press shift, it will open the "Save As" functionality. Additionally, holding shift, then pressing s will type an uppercase S, but if you also press cmd while they are down, you still get the "Save As" menu.

I think this behavior might be too nuanced and application-specific for a primitive, especially if we make assumptions about order in any way. I defer to the maintainers though and just wanted to voice those opinions, thanks for humoring me!

onx2 avatar Jul 03 '25 18:07 onx2