-
Notifications
You must be signed in to change notification settings - Fork 17
Add keyboard hotkey support to menu items #3818
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
Changes from all commits
6288c1c
bd2be74
cff94c1
a09d045
b5cb6c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| :host { | ||
| display: grid; | ||
| grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); | ||
| gap: 2rem; | ||
| } | ||
|
|
||
| limel-example-value { | ||
| grid-column: 1/-1; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import { Component, h, Host, State } from '@stencil/core'; | ||
| import type { LimelHotkeyTriggerDetail } from '@limetech/lime-elements'; | ||
|
|
||
| /** | ||
| * Basic example | ||
| * | ||
| * The value is passed as a string, indicating which hotkey to listen for. | ||
| * | ||
| * The component will automatically detect the operating system, and | ||
| * render the hotkey accordingly, using standard glyphs to save space. | ||
| * | ||
| * For example, the "meta" key will be rendered as <kbd>⌘</kbd> on macOS, | ||
| * and as <kbd>⊞ Win</kbd> on Windows/Linux. Or the "alt" key will be rendered | ||
| * as <kbd>⌥</kbd> on macOS, and as <kbd>Alt</kbd> on Windows. | ||
|
Kiarokh marked this conversation as resolved.
|
||
| * | ||
| * :::note | ||
| * `meta` always means the actual Meta key. | ||
| * | ||
| * This component will render `meta` using platform conventions: | ||
| * - macOS/iOS/iPadOS: <kbd>⌘</kbd> | ||
| * - Windows/Linux: <kbd>⊞ Win</kbd> | ||
| * | ||
| * If you want a hotkey that differs between operating systems (for example | ||
| * ⌘+C on macOS and Ctrl+C on Windows/Linux), detect the OS in your application | ||
| * and pass the appropriate hotkey string. | ||
| * | ||
| * - `ctrl` means “Control specifically” on all platforms. | ||
| * - `cmd` or `command` always render as <kbd>⌘</kbd> (even on Windows/Linux), | ||
| * and are normalized as aliases for `meta` when matching. | ||
| * - Matching hotkeys call `event.preventDefault()` by default, to avoid browser | ||
| * shortcuts (like Save/Print) firing together with your action. Set | ||
| * `preventBrowserDefault={false}` if you need to keep browser defaults. | ||
| * ::: | ||
|
Kiarokh marked this conversation as resolved.
|
||
| */ | ||
| @Component({ | ||
| tag: 'limel-example-hotkey-basic', | ||
| shadow: true, | ||
| styleUrl: 'hotkey-basic.scss', | ||
| }) | ||
| export class HotkeyBasicExample { | ||
| @State() | ||
| private lastSelectedHotkey: string; | ||
|
|
||
| public render() { | ||
| return ( | ||
| <Host onHotkeyTrigger={this.handleHotkeyTrigger}> | ||
| <limel-hotkey value="s" /> | ||
| <limel-hotkey value="alt+s" /> | ||
| <limel-hotkey value="meta+c" /> | ||
| <limel-hotkey value="meta+alt+s" /> | ||
| <limel-hotkey value="meta+enter" /> | ||
| <limel-hotkey value="cmd+enter" /> | ||
|
Kiarokh marked this conversation as resolved.
|
||
| <limel-hotkey value="ctrl+shift+c" /> | ||
| <limel-hotkey value="f1" /> | ||
| <limel-hotkey value="tab" /> | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| <limel-hotkey value="+" /> | ||
| <limel-hotkey value="-" /> | ||
| <limel-example-value | ||
| label="Last triggered hotkey" | ||
| value={this.lastSelectedHotkey} | ||
| /> | ||
| </Host> | ||
| ); | ||
| } | ||
|
|
||
| private handleHotkeyTrigger = ( | ||
|
Check warning on line 66 in src/components/hotkey/examples/hotkey-basic.tsx
|
||
| event: CustomEvent<LimelHotkeyTriggerDetail> | ||
| ) => { | ||
| this.lastSelectedHotkey = event.detail.hotkey; | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { Component, h, Host, State } from '@stencil/core'; | ||
| import type { LimelHotkeyTriggerDetail } from '@limetech/lime-elements'; | ||
|
|
||
| /** | ||
| * The `disabled` prop. | ||
| * When set to `true`, the hotkey is still rendered but will not emit events. | ||
| */ | ||
| @Component({ | ||
| tag: 'limel-example-hotkey-disabled', | ||
| shadow: true, | ||
| styleUrl: 'hotkey-basic.scss', | ||
| }) | ||
| export class HotkeyDisabledExample { | ||
| @State() | ||
| private lastSelectedHotkey: string; | ||
|
|
||
| public render() { | ||
| return ( | ||
| <Host onHotkeyTrigger={this.handleHotkeyTrigger}> | ||
| <limel-hotkey value="a" disabled={true} /> | ||
| <limel-hotkey value="b" /> | ||
| <limel-example-value | ||
| label="Last triggered hotkey" | ||
| value={this.lastSelectedHotkey} | ||
| /> | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| </Host> | ||
| ); | ||
| } | ||
|
|
||
| private handleHotkeyTrigger = ( | ||
|
Check warning on line 30 in src/components/hotkey/examples/hotkey-disabled.tsx
|
||
| event: CustomEvent<LimelHotkeyTriggerDetail> | ||
| ) => { | ||
| this.lastSelectedHotkey = event.detail.hotkey; | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { Component, h, Host, State } from '@stencil/core'; | ||
| import type { LimelHotkeyTriggerDetail } from '@limetech/lime-elements'; | ||
|
|
||
| /** | ||
| * Duplicate hotkeys | ||
| * | ||
| * If multiple enabled `<limel-hotkey>` instances are configured with the same | ||
| * hotkey (after normalization), only the first handler will run for each | ||
| * keypress. This prevents multiple actions from being triggered by a single | ||
| * keyboard event. | ||
| * | ||
| * When a duplicate is detected at runtime, the first handler will log a | ||
| * `console.warn` (once per keypress) to help you spot and fix the conflict. | ||
| * | ||
| * :::note | ||
| * Disabled instances are not counted and never emit events. | ||
| * ::: | ||
| * | ||
| * - Press `2`. | ||
| * - Only one `hotkeyTrigger` will fire (the first handler wins). | ||
| * - A `console.warn` is logged to help you find the duplicate. | ||
| * | ||
| * This behavior is intentional: triggering multiple actions from a single | ||
| * keypress is usually surprising and can be unsafe. | ||
| */ | ||
| @Component({ | ||
| tag: 'limel-example-hotkey-duplicates', | ||
| shadow: true, | ||
| styleUrl: 'hotkey-basic.scss', | ||
| }) | ||
| export class HotkeyDuplicatesExample { | ||
| @State() | ||
| private lastSelectedHotkey: string; | ||
|
|
||
| public render() { | ||
| return ( | ||
| <Host onHotkeyTrigger={this.handleHotkeyTrigger}> | ||
| <limel-hotkey value="2" /> | ||
| <limel-hotkey value="2" /> | ||
| <limel-hotkey value="z" /> | ||
| <limel-hotkey value="z" disabled={true} /> | ||
| <limel-example-value | ||
| label="Last triggered hotkey" | ||
| value={this.lastSelectedHotkey} | ||
| /> | ||
|
Kiarokh marked this conversation as resolved.
|
||
| </Host> | ||
| ); | ||
| } | ||
|
|
||
| private handleHotkeyTrigger = ( | ||
|
Check warning on line 50 in src/components/hotkey/examples/hotkey-duplicates.tsx
|
||
| event: CustomEvent<LimelHotkeyTriggerDetail> | ||
| ) => { | ||
| this.lastSelectedHotkey = event.detail.hotkey; | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import { Component, h, Host, State } from '@stencil/core'; | ||
| import type { LimelHotkeyTriggerDetail } from '@limetech/lime-elements'; | ||
|
|
||
| /** | ||
| * Prevent browser defaults | ||
| * | ||
| * By default, matching hotkeys call `event.preventDefault()` to avoid browser | ||
| * shortcuts (for example Save/Print dialogs) firing together with your app | ||
| * action. | ||
| * | ||
| * Use `preventBrowserDefault={false}` when you explicitly want to keep the | ||
| * browser's native behavior. | ||
| * | ||
| * Try this example: | ||
| * - Keep "Prevent browser defaults" enabled and press | ||
| * <kbd>Ctrl</kbd>/<kbd>⌘</kbd> + <kbd>S</kbd> or <kbd>P</kbd>: | ||
| * browser defaults are prevented. | ||
| * - Disable the switch and press the same hotkeys again: | ||
| * browser defaults are allowed. | ||
| */ | ||
| @Component({ | ||
| tag: 'limel-example-hotkey-prevent-default', | ||
| shadow: true, | ||
| styleUrl: 'hotkey-basic.scss', | ||
| }) | ||
| export class HotkeyPreventDefaultExample { | ||
| @State() | ||
| private lastSelectedHotkey: string; | ||
|
|
||
| @State() | ||
| private preventBrowserDefault = true; | ||
|
|
||
| public render() { | ||
| return ( | ||
| <Host onHotkeyTrigger={this.handleHotkeyTrigger}> | ||
| <limel-hotkey | ||
| value="ctrl+s" | ||
| preventBrowserDefault={this.preventBrowserDefault} | ||
| /> | ||
| <limel-hotkey | ||
| value="ctrl+p" | ||
| preventBrowserDefault={this.preventBrowserDefault} | ||
| /> | ||
| <limel-hotkey | ||
| value="cmd+s" | ||
| preventBrowserDefault={this.preventBrowserDefault} | ||
| /> | ||
| <limel-hotkey | ||
| value="cmd+p" | ||
| preventBrowserDefault={this.preventBrowserDefault} | ||
| /> | ||
| <limel-example-controls style={{ gridColumn: '1 / -1' }}> | ||
| <limel-switch | ||
| value={this.preventBrowserDefault} | ||
| label="Prevent browser defaults" | ||
| onChange={this.handlePreventDefaultToggle} | ||
| /> | ||
| </limel-example-controls> | ||
| <limel-example-value | ||
| label="Last triggered hotkey" | ||
| value={this.lastSelectedHotkey} | ||
| /> | ||
| </Host> | ||
| ); | ||
| } | ||
|
|
||
| private handleHotkeyTrigger = ( | ||
|
Check warning on line 67 in src/components/hotkey/examples/hotkey-prevent-default.tsx
|
||
| event: CustomEvent<LimelHotkeyTriggerDetail> | ||
| ) => { | ||
| this.lastSelectedHotkey = event.detail.hotkey; | ||
| }; | ||
|
|
||
| private handlePreventDefaultToggle = (event: CustomEvent<boolean>) => { | ||
|
Check warning on line 73 in src/components/hotkey/examples/hotkey-prevent-default.tsx
|
||
| event.stopPropagation(); | ||
| this.preventBrowserDefault = event.detail; | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| @forward '../markdown/partial-styles/_kbd.scss'; | ||
| :host(limel-hotkey) { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| gap: 0.25rem; | ||
| } | ||
|
|
||
| :host(limel-hotkey[disabled]:not([disabled='false'])) { | ||
| opacity: 0.5; | ||
| } | ||
|
|
||
| kbd { | ||
| margin: 0; | ||
| font-size: 0.75rem; | ||
| box-shadow: | ||
| var(--button-shadow-pressed), | ||
| 0 0.625rem 0.375px -0.5rem rgb(var(--color-black), 0.02), | ||
| 0 0.025rem 0.5rem 0 rgb(var(--contrast-100)) inset; | ||
| } | ||
|
Kiarokh marked this conversation as resolved.
|
||
|
|
||
| span { | ||
| display: inline-block; | ||
| &::first-letter { | ||
| text-transform: uppercase; | ||
| } | ||
| } | ||
|
|
||
| kbd.is-glyph { | ||
| span { | ||
| transform: scale(1.2); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.