From 4efbf2a1d84171729c073a13bded0289f9f7dec0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=87=8E=E6=BC=AB=E6=A8=AA=E6=B1=9F?= <1399226280@qq.com>
Date: Thu, 5 Jan 2023 09:35:43 +0800
Subject: [PATCH] fixed hotkeys are triggered within the ninja-keys search
input for issue #35
---
README.md | 158 ++++++++++-------
dev/index.html | 295 +++++++++++++++++---------------
src/interfaces/ininja-action.ts | 1 +
src/ninja-keys.ts | 66 ++++---
4 files changed, 301 insertions(+), 219 deletions(-)
diff --git a/README.md b/README.md
index b0e1f6b..d823b89 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ Keyboard shortcut interface for your website that works with Vanilla JS, Vue, an

## Motivation
+
A lot of applications support a common pattern where the user hits ⌘+k (or ctrl+k) and a search UI dialog appears.
I've recently seen this in Notion, Slack, Linear, Vercel and Algolia, but I'm sure there are plenty more.
Apple Spotlight, Alfred and the Raycast app also have a similar pattern, but with different shortcuts.
@@ -24,6 +25,7 @@ so I decided to give it a try for [Web Components](https://open-wc.org/) and [Li
## Integrations
+
- [Vue 3 Example - Codesandbox](https://codesandbox.io/s/ninja-keys-demo-vue3-bps5w?file=/public/index.html)
- [Vue 2 Example - Codesandbox](https://codesandbox.io/s/ninja-keys-demo-vue2-b129z)
- [React Example - Codesandbox](https://codesandbox.io/s/ninja-keys-demo-reactjs-rlv4b?file=/src/App.js)
@@ -33,6 +35,7 @@ so I decided to give it a try for [Web Components](https://open-wc.org/) and [Li
- [SolidJS wrapper](https://github.com/wobsoriano/solid-ninja-keys)
## Features
+
- Keyboard navigation
- Light and dark theme built in
- Built-in icon support from Material font and custom svg icons
@@ -44,24 +47,32 @@ so I decided to give it a try for [Web Components](https://open-wc.org/) and [Li
## Why the "Ninja" name?
+
Because it appears from nowhere and executes any actions quickly...
Or because it allows your users to become keyboard ninjas 🙃
## Install from NPM
+
```bash
npm i ninja-keys
```
+
Import if you are using webpack, rollup, vite or other build system.
+
```js
import 'ninja-keys';
```
## Install from CDN
+
Mostly for usage in HTML/JS without a build system.
+
```html
```
+
or inside your module scripts
+
```html
```
+
Library using flat data structure inside, as in the example above. But you can also use a tree structure as below:
+
```js
{
id: 'Theme',
@@ -142,47 +162,57 @@ Library using flat data structure inside, as in the example above. But you can a
]
}
```
+
## Attributes
+
| Field | Default | Description |
-|----------------------|-----------------------------|-------------------------------------------------------------|
+| -------------------- | --------------------------- | ----------------------------------------------------------- |
| placeholder | Type a command or search... | Placeholder for search |
| disableHotkeys | false | If attribute exist will register all hotkey for all actions |
| hideBreadcrumbs | false | Hide breadcrumbs on header if true |
| openHotkey | cmd+k,ctrl+k | Open or close shortcut |
| navigationUpHotkey | up,shift+tab | Navigation up shortcuts |
| navigationDownHotkey | down,tab | Navigation down shortcuts |
-| closeHotkey | esc | Close shortcut |
+| closeHotkey | esc | Close shortcut |
| goBackHotkey | backspace | Go back on one level if has parent menu |
| selectHotkey | enter | Select action and execute handler or open submenu |
| hotKeysJoinedView | false | If exist/true will display hotkeys inside one element |
| noAutoLoadMdIcons | false | If exist it disable load material icons font on connect |
+
#### Example
+
```html
```
## Data
+
Array of `INinjaAction` - interface properties below
-| Name | Type | Description |
-|----------|-------------------------|----------------------------------------------------------------------------------------|
-| id | string | Unique id/text. Will be displayed as breadcrumb in multimenu |
-| title | string | Title of action |
-| hotkey | string(optional) | Shortcut to display and register |
-| handler | Function(optional) | Function to execute on select |
-| mdIcon | string(optional) | Material Design font icon name |
-| icon | string(optional) | Html to render as custom icon |
-| parent | string(optional) | If using flat structure use id of actions to make a multilevel menu |
-| keywords | string(optional) | Keywords to use for search |
+
+| Name | Type | Description |
+| -------- | ----------------------- | ------------------------------------------------------------ |
+| id | string | Unique id/text. Will be displayed as breadcrumb in multimenu |
+| title | string | Title of action |
+| hotkey | string(optional) | Shortcut to display and register |
+| handler | Function(optional) | Function to execute on select |
+| mdIcon | string(optional) | Material Design font icon name |
+| icon | string(optional) | Html to render as custom icon |
+| parent | string(optional) | If using flat structure use id of actions to make a multilevel menu |
+| keywords | string(optional) | Keywords to use for search |
| children | Array(optional) | If using flat structure then ids of child menu actions. Not required on tree structure |
-| section | string(optional) | Section text. Like a header will be group with other same sections |
+| section | string(optional) | Section text. Like a header will be group with other same sections |
+| keep | boolean(optional) | Whether to keep hotkey monitoring when opening the ninja-keys search input.The default is false |
## Methods
-| Name | Arg | Description |
-|-----------|---------------------|-----------------------------------------------------|
-| `open` | { parent?: string } | Open menu with parent, if null then open root menu |
-| `close` | | Close menu |
-| `setParent` | parent?: string | Navigate to parent menu |
+
+| Name | Arg | Description |
+| ----------- | ------------------- | -------------------------------------------------- |
+| `open` | { parent?: string } | Open menu with parent, if null then open root menu |
+| `close` | | Close menu |
+| `setParent` | parent?: string | Navigate to parent menu |
+
#### Example
+
```js
const ninja = document.querySelector('ninja-keys');
ninja.open()
@@ -191,12 +221,13 @@ ninja.open({ parent: 'Theme' })
```
### Events
+
Component wide events
-| Name | Description | Payload |
-|------------------------------------|-------------------------------------| ------- |
-| `change` | Emitted when on each change of search input | `{ detail: { search: string, actions: Array } }` |
-| `selected` | Emitted when on user selected action or on submit of input | `{ detail: { search: string, action: NinjaAction or undefined }}` |
+| Name | Description | Payload |
+| ---------- | ---------------------------------------------------------- | ------------------------------------------------------------ |
+| `change` | Emitted when on each change of search input | `{ detail: { search: string, actions: Array } }` |
+| `selected` | Emitted when on user selected action or on submit of input | `{ detail: { search: string, action: NinjaAction or undefined }}` |
Both `handler` of action and component event `selected` emitted when user submit form or select item.
@@ -220,38 +251,43 @@ ninja.addEventListener('selected', (event) => {
```
## Themes
+
Component supports a dark theme out-of-box. You just need to add a class.
+
```html
```
If you need more style control, use any of the CSS variables below.
+
### CSS variables
-| Name | Default |
-|------------------------------------|------------------------------------|
-| --ninja-width | 640px; |
-| --ninja-backdrop-filter | none; |
-| --ninja-overflow-background | rgba(255, 255, 255, 0.5); |
-| --ninja-text-color | rgb(60, 65, 73); |
-| --ninja-font-size | 16px; |
-| --ninja-top | 20%; |
-| --ninja-key-border-radius | 0.25em |
-| --ninja-accent-color | rgb(110, 94, 210); |
-| --ninja-secondary-background-color | rgb(239, 241, 244); |
-| --ninja-secondary-text-color | rgb(107, 111, 118); |
-| --ninja-selected-background | rgb(248, 249, 251); |
-| --ninja-icon-color | var(--ninja-secondary-text-color); |
-| --ninja-icon-size | 1.2em; |
-| --ninja-separate-border | 1px solid var(--ninja-secondary-background-color); |
-| --ninja-modal-background | #fff; |
-| --ninja-modal-shadow | rgb(0 0 0 / 50%) 0px 16px 70px; |
-| --ninja-actions-height | 300px; |
-| --ninja-group-text-color | rgb(144, 149, 157); |
-| --ninja-footer-background | rgba(242, 242, 242, 0.4); |
-| --ninja-placeholder-color | #8e8e8e |
-| --ninja-z-index | 1 |
+
+| Name | Default |
+| ---------------------------------- | -------------------------------------------------- |
+| --ninja-width | 640px; |
+| --ninja-backdrop-filter | none; |
+| --ninja-overflow-background | rgba(255, 255, 255, 0.5); |
+| --ninja-text-color | rgb(60, 65, 73); |
+| --ninja-font-size | 16px; |
+| --ninja-top | 20%; |
+| --ninja-key-border-radius | 0.25em |
+| --ninja-accent-color | rgb(110, 94, 210); |
+| --ninja-secondary-background-color | rgb(239, 241, 244); |
+| --ninja-secondary-text-color | rgb(107, 111, 118); |
+| --ninja-selected-background | rgb(248, 249, 251); |
+| --ninja-icon-color | var(--ninja-secondary-text-color); |
+| --ninja-icon-size | 1.2em; |
+| --ninja-separate-border | 1px solid var(--ninja-secondary-background-color); |
+| --ninja-modal-background | #fff; |
+| --ninja-modal-shadow | rgb(0 0 0 / 50%) 0px 16px 70px; |
+| --ninja-actions-height | 300px; |
+| --ninja-group-text-color | rgb(144, 149, 157); |
+| --ninja-footer-background | rgba(242, 242, 242, 0.4); |
+| --ninja-placeholder-color | #8e8e8e |
+| --ninja-z-index | 1 |
#### Example
+
```css
ninja-keys {
--ninja-width: 400px;
@@ -259,20 +295,22 @@ ninja-keys {
```
### CSS Shadow Parts
+
Allowing you to style specific elements from your style.
Because styles are encapsulated by Shadow DOM, it will be annoying to create css variables for all properties.
That's why you can use `::part` to make a custom look for the component.
It's supported by [all modern browsers](https://caniuse.com/mdn-css_selectors_part)
-| Name | Description |
-|------------------------------------|-------------------------------------|
-| actions-list | Element that wraps all child elements. |
-| ninja-action | Single action |
-| ninja-selected | Selected action |
-| ninja-input | Input element |
-| ninja-input-wrapper | Wrapper element around div, useful for advanced styles|
+| Name | Description |
+| ------------------- | ------------------------------------------------------ |
+| actions-list | Element that wraps all child elements. |
+| ninja-action | Single action |
+| ninja-selected | Selected action |
+| ninja-input | Input element |
+| ninja-input-wrapper | Wrapper element around div, useful for advanced styles |
#### Example style using parts
+
```css
ninja-keys::part(actions-list) {
padding: 8px;
@@ -301,17 +339,20 @@ ninja-keys::part(ninja-input-wrapper) {
```
### Icons
+
By default, components use icons from [https://fonts.google.com/icons](https://fonts.google.com/icons)
For example, you can just set `mdIcon` to `light_mode` to render a sun icon.
To add Material icons for your website, you need to add them to your HTML, for example
+
```html
```
If you want custom icons, you can use `svg` or `img` to insert it with an `icon` property for action with `ninja-icon` class.
Example:
+
```js
{
title: 'Search projects...',
@@ -321,7 +362,9 @@ Example:
section: 'Projects',
},
```
+
You can also change the width and font using CSS variables
+
```css
ninja-keys {
--ninja-icon-size: 1em;
@@ -330,6 +373,7 @@ ninja-keys {
### Change or hide footer
+
```html
You can use a custom footer or empty div to hide it
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas gravida
+ quis nulla et dignissim. Quisque imperdiet turpis eget augue mollis
+ varius. Morbi eget molestie velit. Vivamus nec vulputate odio, eget
+ ultrices magna. Morbi vehicula tincidunt aliquet. Mauris massa lacus,
+ iaculis in magna ut, pretium lobortis nibh. Praesent pulvinar nunc vel
+ blandit facilisis. Sed suscipit fringilla odio et malesuada. Quisque at
+ nulla ac ipsum sollicitudin faucibus eleifend id eros. Fusce tempor metus
+ id lacus ornare, eget fringilla justo fermentum. Aenean semper nunc sed
+ ligula tincidunt, in venenatis est feugiat. Vivamus aliquam tristique
+ libero in cursus. Praesent orci turpis, auctor id congue et, cursus sed
+ quam. Vivamus commodo mattis lacus id molestie. Donec eget venenatis
+ libero. Nam pharetra sollicitudin dui, mollis dictum dolor sollicitudin
+ nec. Vivamus mi est, blandit in lectus condimentum, bibendum ullamcorper
+ libero. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Vestibulum ac sollicitudin leo, sit amet convallis lectus. Quisque ut
+ purus ullamcorper, euismod orci vitae, vestibulum tellus. Proin porta
+ nulla eleifend arcu pulvinar posuere. Etiam iaculis elit nec vehicula
+ blandit. Mauris urna arcu, tempor vitae dictum quis, tempor eu orci. Donec
+ faucibus turpis ac ligula cursus lobortis. Fusce elementum tempus nisi eu
+ ullamcorper. Fusce fermentum nibh magna, eget sollicitudin ex maximus
+ quis. Quisque ornare vitae augue at posuere. Sed in urna sodales, sodales
+ justo vitae, tristique nisl. Pellentesque habitant morbi tristique
+ senectus et netus et malesuada fames ac turpis egestas. Aenean mollis
+ blandit dictum. Aenean id laoreet purus, et pulvinar nibh. Phasellus vitae
+ condimentum sapien, a pretium est. Aenean mollis semper euismod.
+ Pellentesque dapibus, odio non scelerisque vehicula, arcu enim molestie
+ purus, at tristique quam massa sed tellus. Donec ut metus enim. Interdum
+ et malesuada fames ac ante ipsum primis in faucibus. Donec sit amet
+ iaculis risus. Vivamus vel felis nec dui posuere ultricies. Nunc placerat,
+ nisl at consectetur venenatis, sapien justo auctor quam, ac mollis quam
+ nibh id mi. Integer sit amet vulputate lectus. Aliquam dignissim feugiat
+ rhoncus. Suspendisse vestibulum tincidunt sollicitudin. Donec varius purus
+ a nisl ultrices venenatis quis ac velit. Ut molestie porttitor justo, vel
+ auctor neque aliquam nec. Pellentesque sagittis tempus dolor. In vel
+ turpis tincidunt, bibendum nibh at, tempus quam. Integer eget tempus quam.
+ Quisque fringilla efficitur augue, id convallis enim interdum non. Nam
+ varius urna ac scelerisque aliquet. Integer tempus auctor condimentum.
+ Pellentesque faucibus nunc et felis fermentum, quis varius leo porttitor.
+