Skip to content

Add support for keyboard and mouse button modifiers for the on: directive. #4427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
rdytogokev opened this issue Feb 18, 2020 · 8 comments
Closed

Comments

@rdytogokev
Copy link

Is your feature request related to a problem? Please describe.
I am coming from Vue.js and really like Svelte and the concepts it promotes.

One feature that Vue offers that I have become spoiled by, however, is their "Key" and "Mouse Button" modifiers for event listeners. I have noticed that Svelte also has modifiers for the on: directive. Which is coded like: on:eventname|modifiers={handler}. This is documented on svelte.dev.

This is great; but when listening for keyboard or mouse-specific events like keyup, keydown, or middle (a mouse button modifier); Vue goes the extra mile by providing additional modifiers that make coding a real pleasure. Here is an example: <input @keyup.enter="doSomething">. Now I don't have to code a keyCode check for the enter key. WooHoo!

An example of how this might look in Svelte is on:keyup.enter={doSomething}. With these nuanced modifiers the lazy spoiled coder that I am no longer has to add this line in my 'doSomething' function: if (event.key !== 'Enter') return;

Describe the solution you'd like
I would love it if all the modifiers that Vue has, could be added to the on: directive. This includes these keyboard modifiers: .enter, .tab, .delete, .esc, .space, .up, .down, .left, .right, .ctrl, .alt, .shift, and .meta
and these mouse button modifiers: .left, .right, and .middle,
as well as the .exact modifier.

The documentation and code can be found here:
Vue Key Modifiers Documentation
Vue System Key Modifiers Documentation
'v-on' API Documentation
'v-on' Source Code

Describe alternatives you've considered
So far I have just hand-coded something like this in the event handler function: if (event.key !== 'Enter'

How important is this feature to you?
This would be a nice extra to have, but I can do without it if you really don't want to provide. It just makes coding in Vue templates a real pleasure. One that I would love to see Svelte rock!

Additional note
I just wanted to take a moment to thank you for all the work you have done and your time considering this request.

@burningTyger
Copy link
Contributor

I can see how this is tempting to add but if you look at the actual code you need to write it yourself you're saving at most a handful of characters:

<script>
	let name = 'world';
	const action = _ => name='Enter'
</script>

<input type="text" on:keyup={e=>e.key==='Enter' && action()}/>
<h1>
	{name}
</h1>

@antony
Copy link
Member

antony commented Feb 18, 2020

I sort of like the proposal, but I feel like the point @burningTyger has made is very much more in the Svelte mindset - rather than have a huge expansive API, extensive documentation, and steep learning curve, we can rely on features the language / browsers already provide in order to make Svelte a powerful yet simple solution for building interfaces.

@Conduitry
Copy link
Member

This has come up before in this comment and (sort of) in this issue and probably in other places. I'm also of the mind that this is something that should just be done in javascript, rather than adding to svelte's API.

@anewton1998
Copy link

This Svelte addon project maybe appropriate: https://github.com/metonym/svelte-keydown

@johnnysprinkles
Copy link

johnnysprinkles commented Sep 11, 2021

What about mouse buttons though? I can see why adding this for every keyboard key would expand the API surface too much, but it sure would be nice if we could detect left clicks and mousedowns easily such as:

on:mousedown|right={showContextMenu}
on:click|left={handleClick}

@antony
Copy link
Member

antony commented Sep 11, 2021 via email

@rogadev
Copy link

rogadev commented Aug 23, 2022

It makes absolutely no sense why you'd have on:submit|preventDefault but not on:keypress|enter. I think this is a missed opportunity that should be revisited if it hasn't already. @rdytogokev makes a valuable point that it is a well established design pattern in other frameworks. To exclude the same simplified, well established pattern from Svelte is doing Svelte a great disservice.

@Evertt
Copy link

Evertt commented Nov 6, 2022

I tend to agree with rogadev and everybody else who is in favor of this feature. Yes I know the javascript equivalent isn't that much more work, but it does add boilerplate that is annoying and feels so unnecessary. Also it's just less pleasant to read. And finally, if you guys would add this feature, then the VSCode extension for svelte could help adding auto-completion for the keys that you'd want to capture. Because at the moment, I almost always need to spend some time looking up how the key I want to catch for is spelled exactly.

Then again, I've never contributed to the svelte compiler, so I don't know exactly how much more code it would add to the compiler that would need to be maintained. But honestly, I can't imagine that it would be that much code. Because the general code for modifiers already exists. So I'm guessing the only code that needs to be added to the compiler is something along the lines of this:

const keyboardEventCodesAndKeys = [
    'Backspace', 'Tab', 'Enter', 'Pause', 'Escape',
    'Space', 'PageUp', 'PageDown', 'End', 'Home',
    'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown',
    'PrintScreen', 'Insert', 'Delete',

    // This list mixes both lists of possible
    // event.code and event.key values
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    'Digit0', 'Digit1', 'Digit2', 'Digit3', 'Digit4',
    'Digit5', 'Digit6', 'Digit7', 'Digit8', 'Digit9',

    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
    'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    'KeyA', 'KeyB', 'KeyC', 'KeyD', 'KeyE', 'KeyF', 'KeyG',
    'KeyH', 'KeyI', 'KeyJ', 'KeyK', 'KeyL', 'KeyM', 'KeyN',
    'KeyO', 'KeyP', 'KeyQ', 'KeyR', 'KeyS', 'KeyT', 'KeyU',
    'KeyV', 'KeyW', 'KeyX', 'KeyY', 'KeyZ', 'ContextMenu',

    'Numpad0', 'Numpad1', 'Numpad2', 'Numpad3', 'Numpad4',
    'Numpad5', 'Numpad6', 'Numpad7', 'Numpad8', 'Numpad9',
    'NumpadMultiply', 'NumpadAdd', 'NumpadSubtract',
    'NumpadDecimal', 'NumpadDivide', 'NumLock', 'ScrollLock',
    'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',

    'AudioVolumeMute', 'AudioVolumeDown', 'AudioVolumeUp',
    'LaunchMediaPlayer', 'LaunchApplication1', 'LaunchApplication2',

    // Firefox uses these instead of the ones above
    'VolumeMute', 'VolumeDown', 'VolumeUp',
    'MediaSelect', 'LaunchApp1', 'LaunchApp2',

    'Semicolon', 'Equal', 'Comma', 'Minus', 'Period',
    'Slash', 'Backslash', 'Quote', 'Backquote',
    'BracketLeft', 'BracketRight',
];

const getKeyboardEventModifierKeys = (event: KeyboardEvent) = ({
    ctrl: event.ctrlKey,
    alt: event.altKey,
    shift: event.shiftKey,
    meta: event.metaKey,
    get ctrlOrMeta() {
        // this is to accomodate
        // both windows and mac users
        return this.ctrl || this.meta
    }
});

if (eventListener.type === event.type) { // event.type being one of keyup, keydown or keypress
    if (eventListener.hasModifiers()) { // Or however you guys write this in the svelte compiler
        const eventModifiers = eventListener.getModifiers()
        // I'm assuming that if it was written like this in the svelte component: `on:keypress|ctrlOrMeta|alt|z`,
        // that then eventListener.getModifiers() would return ['ctrlOrMeta', 'alt', 'z']

        const keyboardEventModifierKeys = getKeyboardEventModifierKeys(event)

        // eventModifierKeys will be ['ctrlOrMeta', 'alt']
        const eventModifierKeys = eventModifiers.filter(key => key in keyboardEventModifierKeys);

        // eventKeyboardCodeOrKey will be 'z'
        const eventKeyboardCodeOrKey = eventModifiers.find(key => keyboardEventCodesAndKeys.includes(key));

        if (eventKeyboardCodeOrKey && [event.code, event.key].includes(eventKeyboardCodeOrKey)) {
            return; // Event code / key did not match eventKeyboardCodeOrKey, so we ignore this event.
        }

        // This basically checks if (event.ctrlKey === true || event.metaKey === true) && event.altKey === true
        if (!eventModifierKeys.every(modifierKey => keyboardEventModifierKeys[modifierKey])) {
            return; // Not all the specified event modifiers keys were active, so we ignore this event.
        }

        // And then here of course you also need to do the existing checks for |preventDefault and |once, etc...
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants