Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions etc/lime-elements.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,12 @@ export namespace Components {
"length"?: number;
"maxLength"?: number;
}
// @internal
export interface LimelHotkey {
"disabled": boolean;
"preventBrowserDefault": boolean;
"value": string;
}
export interface LimelIcon {
"badge": boolean;
"name": string;
Expand Down Expand Up @@ -623,6 +629,8 @@ export namespace Components {
export interface LimelMenuItemMeta {
"badge"?: string | number;
"commandText"?: string;
"disabled": boolean;
"hotkey"?: string;
"showChevron": boolean;
}
// @internal (undocumented)
Expand Down Expand Up @@ -1372,6 +1380,10 @@ export namespace JSX {
//
// (undocumented)
"limel-helper-line": Omit<LimelHelperLine, keyof LimelHelperLineAttributes> & { [K in keyof LimelHelperLine & keyof LimelHelperLineAttributes]?: LimelHelperLine[K] } & { [K in keyof LimelHelperLine & keyof LimelHelperLineAttributes as `attr:${K}`]?: LimelHelperLineAttributes[K] } & { [K in keyof LimelHelperLine & keyof LimelHelperLineAttributes as `prop:${K}`]?: LimelHelperLine[K] };
// Warning: (ae-incompatible-release-tags) The symbol ""limel-hotkey"" is marked as @public, but its signature references "JSX" which is marked as @internal
//
// (undocumented)
"limel-hotkey": Omit<LimelHotkey, keyof LimelHotkeyAttributes> & { [K in keyof LimelHotkey & keyof LimelHotkeyAttributes]?: LimelHotkey[K] } & { [K in keyof LimelHotkey & keyof LimelHotkeyAttributes as `attr:${K}`]?: LimelHotkeyAttributes[K] } & { [K in keyof LimelHotkey & keyof LimelHotkeyAttributes as `prop:${K}`]?: LimelHotkey[K] };
// (undocumented)
"limel-icon": Omit<LimelIcon, keyof LimelIconAttributes> & { [K in keyof LimelIcon & keyof LimelIconAttributes]?: LimelIcon[K] } & { [K in keyof LimelIcon & keyof LimelIconAttributes as `attr:${K}`]?: LimelIconAttributes[K] } & { [K in keyof LimelIcon & keyof LimelIconAttributes as `prop:${K}`]?: LimelIcon[K] };
// (undocumented)
Expand Down Expand Up @@ -2474,6 +2486,24 @@ export namespace JSX {
"maxLength": number;
}

// @internal
export interface LimelHotkey {
"disabled"?: boolean;
"onHotkeyTrigger"?: (event: LimelHotkeyCustomEvent<LimelHotkeyTriggerDetail>) => void;
"preventBrowserDefault"?: boolean;
"value"?: string;
}

// (undocumented)
export interface LimelHotkeyAttributes {
// (undocumented)
"disabled": boolean;
// (undocumented)
"preventBrowserDefault": boolean;
// (undocumented)
"value": string;
}

export interface LimelIcon {
"badge"?: boolean;
"name"?: string;
Expand Down Expand Up @@ -2764,6 +2794,8 @@ export namespace JSX {
export interface LimelMenuItemMeta {
"badge"?: string | number;
"commandText"?: string;
"disabled"?: boolean;
"hotkey"?: string;
"showChevron"?: boolean;
}

Expand All @@ -2774,6 +2806,10 @@ export namespace JSX {
// (undocumented)
"commandText": string;
// (undocumented)
"disabled": boolean;
// (undocumented)
"hotkey": string;
// (undocumented)
"showChevron": boolean;
}

Expand Down Expand Up @@ -3807,6 +3843,23 @@ export interface LimelFormCustomEvent<T> extends CustomEvent<T> {
target: HTMLLimelFormElement;
}

// Warning: (ae-missing-release-tag) "LimelHotkeyCustomEvent" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface LimelHotkeyCustomEvent<T> extends CustomEvent<T> {
// (undocumented)
detail: T;
// (undocumented)
target: HTMLLimelHotkeyElement;
}

// @public
export type LimelHotkeyTriggerDetail = {
hotkey: string;
value: string;
keyboardEvent: KeyboardEvent;
};

// Warning: (ae-missing-release-tag) "LimelInputFieldCustomEvent" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
Expand Down Expand Up @@ -4090,6 +4143,7 @@ interface MenuItem<T = any> {
badge?: number | string;
commandText?: string;
disabled?: boolean;
hotkey?: string;
icon?: string | Icon;
// @deprecated
iconColor?: Color;
Expand Down
9 changes: 9 additions & 0 deletions src/components/hotkey/examples/hotkey-basic.scss
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;
}
71 changes: 71 additions & 0 deletions src/components/hotkey/examples/hotkey-basic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Component, h, Host, State } from '@stencil/core';
Comment thread
Kiarokh marked this conversation as resolved.
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.
Comment thread
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.
* :::
Comment thread
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" />
Comment thread
Kiarokh marked this conversation as resolved.
<limel-hotkey value="ctrl+shift+c" />
<limel-hotkey value="f1" />
<limel-hotkey value="tab" />
Comment thread
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Member 'handleHotkeyTrigger' is never reassigned; mark it as `readonly`.

See more on https://sonarcloud.io/project/issues?id=Lundalogik_lime-elements&issues=AZwtUzB70HVnWgCugVoe&open=AZwtUzB70HVnWgCugVoe&pullRequest=3818
event: CustomEvent<LimelHotkeyTriggerDetail>
) => {
this.lastSelectedHotkey = event.detail.hotkey;
};
}
35 changes: 35 additions & 0 deletions src/components/hotkey/examples/hotkey-disabled.tsx
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}
/>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</Host>
);
}

private handleHotkeyTrigger = (

Check warning on line 30 in src/components/hotkey/examples/hotkey-disabled.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Member 'handleHotkeyTrigger' is never reassigned; mark it as `readonly`.

See more on https://sonarcloud.io/project/issues?id=Lundalogik_lime-elements&issues=AZwtlol1Uii2vnNJg0Dr&open=AZwtlol1Uii2vnNJg0Dr&pullRequest=3818
event: CustomEvent<LimelHotkeyTriggerDetail>
) => {
this.lastSelectedHotkey = event.detail.hotkey;
};
}
55 changes: 55 additions & 0 deletions src/components/hotkey/examples/hotkey-duplicates.tsx
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}
/>
Comment thread
Kiarokh marked this conversation as resolved.
</Host>
);
}

private handleHotkeyTrigger = (

Check warning on line 50 in src/components/hotkey/examples/hotkey-duplicates.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Member 'handleHotkeyTrigger' is never reassigned; mark it as `readonly`.

See more on https://sonarcloud.io/project/issues?id=Lundalogik_lime-elements&issues=AZwtlt7soquUoZxK4LoA&open=AZwtlt7soquUoZxK4LoA&pullRequest=3818
event: CustomEvent<LimelHotkeyTriggerDetail>
) => {
this.lastSelectedHotkey = event.detail.hotkey;
};
}
77 changes: 77 additions & 0 deletions src/components/hotkey/examples/hotkey-prevent-default.tsx
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Member 'handleHotkeyTrigger' is never reassigned; mark it as `readonly`.

See more on https://sonarcloud.io/project/issues?id=Lundalogik_lime-elements&issues=AZyJ7ErIfrFQwtlI-6Gb&open=AZyJ7ErIfrFQwtlI-6Gb&pullRequest=3818
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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Member 'handlePreventDefaultToggle' is never reassigned; mark it as `readonly`.

See more on https://sonarcloud.io/project/issues?id=Lundalogik_lime-elements&issues=AZyJ7ErIfrFQwtlI-6Gc&open=AZyJ7ErIfrFQwtlI-6Gc&pullRequest=3818
event.stopPropagation();
this.preventBrowserDefault = event.detail;
};
}
33 changes: 33 additions & 0 deletions src/components/hotkey/hotkey.scss
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;
}
Comment thread
Kiarokh marked this conversation as resolved.

span {
display: inline-block;
&::first-letter {
text-transform: uppercase;
}
}

kbd.is-glyph {
span {
transform: scale(1.2);
}
}
Loading
Loading