├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierrc ├── .release-it.json ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── generateDocSnippets.js ├── package-lock.json ├── package.json ├── playwright.config.ts ├── postcss.config.cjs ├── snippets ├── command-palette.txt ├── paletteStore.txt └── styling.txt ├── src ├── app.css ├── app.d.ts ├── app.html ├── components │ ├── Counter.svelte │ ├── Document.svelte │ ├── Features.svelte │ ├── Hero.svelte │ ├── KeyboardShortcut.svelte │ ├── Navbar.svelte │ └── Sidebar.svelte ├── lib │ ├── components │ │ ├── CommandPalette.svelte │ │ ├── KeyboardButton.svelte │ │ ├── Portal.svelte │ │ ├── Result.svelte │ │ ├── ResultPanel.svelte │ │ └── index.js │ ├── constants │ │ └── index.js │ ├── index.ts │ ├── store │ │ └── PaletteStore.ts │ ├── types │ │ └── index.d.ts │ └── utils │ │ ├── createActionMap.ts │ │ ├── createShortcuts.ts │ │ ├── createStoreMethods.ts │ │ └── index.ts ├── routes │ ├── __layout-doc.svelte │ ├── __layout.svelte │ ├── code │ │ ├── code.json.js │ │ ├── command-palette.html │ │ ├── paletteStore.html │ │ └── styling.html │ ├── docs │ │ ├── __layout@doc.svelte │ │ ├── command-palette-api.svelte │ │ ├── define-actions.svelte │ │ ├── index.svelte │ │ ├── palette-store.svelte │ │ └── styling.svelte │ └── index.svelte ├── store │ └── themeStore.ts └── utils │ └── switchTheme.js ├── static ├── favicon.png └── tailwind.png ├── svelte.config.js ├── tailwind.config.cjs ├── tests └── test.ts └── tsconfig.json /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "npm": { 3 | "publishPath": "./package" 4 | }, 5 | "github": { 6 | "release": true, 7 | "releaseName": "v${version}" 8 | }, 9 | "git": { 10 | "commitMessage": "Craft v${version} release", 11 | "requireCleanWorkingDir": true, 12 | "tagAnnotation": "Release v${version}", 13 | "tagName": "v${version}" 14 | }, 15 | "hooks": { 16 | "before:init": ["npm run pre:package-release"], 17 | "after:bump": "npm run package", 18 | "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}." 19 | } 20 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Rohit Kashyap 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte Command Palette 2 | 3 | #### Increase your productivity exponentially. 🚀🚀 4 | 5 | #### Get started with command-palette with 2.1Kb Minified and ~700B Gzipped + Minified 6 | 7 | ## Demo 8 | 9 | ![svelte-command-palette](https://rohit-misc.s3.ap-south-1.amazonaws.com/svelte-command-palette.gif?response-content-disposition=inline&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEAcaCmFwLXNvdXRoLTEiSDBGAiEArQtcSxpqlkpMiIXIRbzim6ygEdu%2FiWM0JafqMC67%2F%2FMCIQCyrjWfyugvigPDcM3KKLDMpybs7WuY6ia33eTzPtydBCrkAghAEAMaDDg0NzkwMjcyNzUyNCIMU0ZdGeUyXrP3cS%2BAKsECrRS0rQ1645z%2F1SjI2gh2nTd5z4HWvh4VO68TdetQudGZhAvWtAbqUi58xbKG10FtENPhIu%2FRxwOeDOF%2BWxXaEFisWKEcruAG1oo4s%2BMn%2BVGJwgIqNrXAG8XfuSviCT68XXPp2YZEnZ4RzFtxNxpsYfkEghpwtbXjATxmwnADyZQ4cogwScgUxZ7HaJkEm%2BSbEmSfj0nJ1AuY%2F%2BmP7AMHSvVA4ut7MaFtOtOb4jtSamsxmAv6JNeCmceGtAdzxXp0KWahWecXII4dR8fhY8ECygj3hQx23d90XBhmzzJvNW9EZj%2BTwSL4N43kjo4Bf9rDUuHdvs5nXHfYy%2B9tDj0P%2FmxE2A06zc8bq3wRHMa6oS5HCz5i2YdxkmcDo%2Fiwf%2FN%2BmzQyH%2FHkmcSUcu%2FQl7qx0M5lvhK91cujDasj8gOOM6hzMKOKr5YGOrIC8ycy1e2b2krfyhpHtYjRX1MkiOQ25oqZDwOZarY1iwrwNrvc%2FPzZoQvY%2FAFrf8%2BfbRUU6yC5RtJVBOzD8Fc207FethWt%2FLpPEHcB8APtM2wX%2FGFbTVUsNyb4zsTGfdgBdzY6HJbho0%2Fqsy7%2F%2FF0FKgInX3FLec3EFK3t%2BD10%2B6O72yO%2BsseBujEiqpOdnJahnUwycc9b4BQcltw3miOr7LOZUKqebSm8wt4z2VPBsTYTl8PMtfYr1%2BXzhTqTnfkgI3V9aYM4zagE4geh5SsJ5Jw%2FVKP8ueONkSylwupnI01PPRjs9KtYHLU7PIXRooUtbU21iK7JHn%2Fp0FmJEa50EhY30lGsB1ZaOaFO%2B2XzEH%2FYnuNCpiXyveUky9Y0h%2FTfd2bLdlGwLEc8sHRoHUqS7lVe&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220711T064152Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIA4K2XQ3VSAZXZYFHE%2F20220711%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Signature=553d6bef29fd1c97022ab3a4db0af5505b9b6d89d925ddf7e3745375588c8f62) 10 | 11 | ## Features 12 | 13 | - Fuzzy search powered by fuse.js 14 | - Run actions conditionally 15 | - Super simple API, just define your actions and it just works! 16 | - Advanced conditional actions - decide whether to run your action based on the current state of your command palette 17 | - Access to `paletteStore` , update your update palette store from anywhere to make updates to your command-palette. 18 | - Keyboard shortcuts - define keyboard shortcuts for your actions! 19 | 20 | and more 21 | 22 | ## Installation 23 | 24 | Install svelte-command-palette with npm 25 | 26 | ```bash 27 | npm install svelte-command-palette 28 | ``` 29 | 30 | ## Usage/Examples 31 | 32 | ```svelte 33 | 51 | 52 | // render your command palette at the root of your application, say _layout.svelte 53 | 54 | 55 | 56 | 57 | 58 | 59 | ``` 60 | 61 | ## API 62 | 63 | ### Component Styling API 64 | 65 | The `` component accepts the following optional properties for styling: 66 | 67 | | Property | Type | Default | Description | 68 | | ---------------------------- | -------------------------------- | ---------------------- | ---------------------------------------------------------------------- | 69 | | **unstyled** | boolean | `false` | When `true`, the default styles are not applied to the modal elements. | 70 | | **placeholder** | string | `"Search for actions"` | Placeholder for the command palette input | 71 | | **overlayClass** | string \| null | `null` | Class name for the palette overlay. | 72 | | **paletteWrapperInnerClass** | string \| null | `null` | Class name for the cmd palette wrapper element. | 73 | | **inputClass** | string \| null | `null` | Class name for the cmd palette input. | 74 | | **resultsContainerClass** | string \| null | `null` | Class name for the results container. | 75 | | **resultContainerClass** | string \| null | `null` | Class name for the result item container. | 76 | | **optionSelectedClass** | string \| null | `null` | Class name for the currently selected result item. | 77 | | **titleClass** | string \| null | `null` | Class name for the result title. | 78 | | **subtitleClass** | string \| null | `null` | Class name for the result subtitle. | 79 | | **descriptionClass** | string \| null | `null` | Class name for the result description. | 80 | | **keyboardButtonClass** | string \| null | `null` | Class name for the keyboard shortcuts. | 81 | | **overlayStyle** | Record | `{}` | Style properties of the overlay. | 82 | | **paletteWrapperInnerStyle** | Record | `{}` | Style properties of the command palette wrapper element. | 83 | | **inputStyle** | Record | `{}` | Style properties of cmd palette input. | 84 | | **resultsContainerStyle** | Record | `{}` | Style properties of results container. | 85 | | **resultContainerStyle** | Record | `{}` | Style properties result item container. | 86 | | **titleStyle** | Record | `{}` | Style properties for result item title. | 87 | | **subtitleStyle** | Record | `{}` | Style properties for result item subtitle. | 88 | | **descriptionStyle** | Record | `{}` | Style properties for result item description. | 89 | | **optionSelectedStyle** | Record | `{}` | Style properties selected result item. | 90 | | **keyboardButtonStyle** | Record | `{}` | Style properties for the keyboard shortcut. | 91 | 92 | ## Action API 93 | 94 | ``` 95 | actionId?: ActionId; 96 | canActionRun?: (args: onRunParams) => boolean; 97 | title: string; 98 | description?: string; 99 | subTitle?: string; 100 | onRun?: (args: onRunParams) => void; 101 | keywords?: Array; 102 | shortcut?: string; 103 | ``` 104 | 105 | ## Store Methods 106 | 107 | Get palette methods from `createStoreMethods`. 108 | 109 | ``` 110 | import { createStoreMethods } from 'svelte-command-palette` 111 | 112 | const methods = createStoreMethods(); 113 | 114 | method.openPalette(); 115 | ``` 116 | 117 | ### API 118 | 119 | ``` 120 | togglePalette: () => void; 121 | getAllCalledActions: () => ActionId[]; 122 | storeCalledAction: (id: ActionId) => void; 123 | revertLastAction: (id: ActionId) => void; 124 | resetActionLog: () => void; 125 | openPalette: () => void; 126 | closePalette: () => void; 127 | resetPaletteStore: () => void; 128 | ``` 129 | -------------------------------------------------------------------------------- /generateDocSnippets.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | import shiki from 'shiki'; 4 | 5 | export const generateSnippets = async () => { 6 | const __dirname = path.dirname(''); 7 | const files = await fs.readdir(path.join(__dirname, './snippets')); 8 | const highlighter = await shiki.getHighlighter({ theme: 'github-dark-dimmed' }); 9 | // for each file, convert to html 10 | const generatedMarkup = files.map(async (file) => { 11 | console.log(`Generating docs: ${file}`); 12 | const content = await fs.readFile(path.join(__dirname, `./snippets/${file}`), 'utf-8'); 13 | const html = highlighter.codeToHtml(content, { lang: 'svelte' }); 14 | return { 15 | html, 16 | fileName: file 17 | }; 18 | }); 19 | const generatedHTML = await Promise.all(generatedMarkup); 20 | // write each geneated to code/*.html 21 | const writeFiles = generatedHTML.map(async ({ fileName, html }) => { 22 | fileName = fileName.replace('.txt', ''); 23 | try { 24 | await fs.unlink(path.join(__dirname, `./src/routes/code/${fileName}.html`)); 25 | } catch (e) { 26 | // 27 | } 28 | return fs.writeFile(path.join(__dirname, `./src/routes/code/${fileName}.html`), html, { 29 | encoding: 'utf8', 30 | flag: 'w' 31 | }); 32 | }); 33 | await Promise.all(writeFiles); 34 | return generatedHTML; 35 | }; 36 | 37 | generateSnippets(); 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-command-palette", 3 | "version": "1.2.0", 4 | "scripts": { 5 | "build:docs": "node generateDocSnippets.js", 6 | "dev": "npm run build:docs && svelte-kit dev", 7 | "build": "npm run build:docs && svelte-kit build", 8 | "package": "svelte-kit package && cp ./README.md ./package/README.md", 9 | "preview": "svelte-kit preview", 10 | "prepare": "svelte-kit sync", 11 | "test": "playwright test", 12 | "check": "svelte-check --tsconfig ./tsconfig.json", 13 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 14 | "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", 15 | "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .", 16 | "release": "release-it", 17 | "pre:package-release": "rm -rf ./package" 18 | }, 19 | "devDependencies": { 20 | "@playwright/test": "^1.21.0", 21 | "@sveltejs/adapter-auto": "next", 22 | "@sveltejs/kit": "next", 23 | "@typescript-eslint/eslint-plugin": "^5.10.1", 24 | "@typescript-eslint/parser": "^5.10.1", 25 | "autoprefixer": "^10.4.7", 26 | "eslint": "^8.12.0", 27 | "eslint-config-prettier": "^8.3.0", 28 | "eslint-plugin-svelte3": "^4.0.0", 29 | "postcss": "^8.4.14", 30 | "prettier": "^2.5.1", 31 | "prettier-plugin-svelte": "^2.5.0", 32 | "release-it": "^15.1.1", 33 | "shiki": "^0.10.1", 34 | "svelte": "^3.44.0", 35 | "svelte-check": "^2.2.6", 36 | "svelte-preprocess": "^4.10.7", 37 | "tailwindcss": "^3.1.3", 38 | "tslib": "^2.3.1", 39 | "typescript": "~4.6.2" 40 | }, 41 | "type": "module", 42 | "dependencies": { 43 | "csstype": "^3.1.0", 44 | "fuse.js": "^6.6.2", 45 | "i": "^0.3.7", 46 | "npm": "^8.10.0", 47 | "svelte-command-palette": "^1.1.4", 48 | "svelte2tsx": "^0.5.10", 49 | "tinykeys": "^1.4.0" 50 | }, 51 | "publishConfig": { 52 | "access": "public" 53 | }, 54 | "keywords": [ 55 | "svelte", 56 | "svelte-command-palette", 57 | "command-palette", 58 | "cmd-palette", 59 | "palette", 60 | "sveltejs", 61 | "sveltejs command palette" 62 | ], 63 | "contributors": [ 64 | { 65 | "email": "rohit.mmm1996@gmail.com", 66 | "name": "Rohit Kashyap", 67 | "url": "https://rohitpotato.vercel.app/" 68 | } 69 | ], 70 | "repository": { 71 | "type": "git", 72 | "url": "https://github.com/rohitpotato/svelte-command-palette.git" 73 | }, 74 | "homepage": "https://svelte-command-palette.vercel.app/" 75 | } 76 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 3000 7 | } 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /snippets/command-palette.txt: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /snippets/paletteStore.txt: -------------------------------------------------------------------------------- 1 | import { paletteStore } from 'svelte-command-palette'; 2 | 3 | // Update your paletteStore from anywhere in the application 4 | 5 | paletteStore.update((storeValue) => { 6 | return { 7 | ...storeValue, 8 | customValue: 'new value!' 9 | } 10 | }) 11 | 12 | // You can use this custom value in your onRun / canActionRun 13 | 14 | { 21 | console.log(storeProps.customValue) 22 | // new value! 23 | }, 24 | // decide whether to run the action based on your palette state 25 | canActionRun: ({ action, storeProps, storeMethods }) => { 26 | if(storeProps.customValue === 'old value') { 27 | return false 28 | } 29 | return true 30 | }, 31 | 32 | } 33 | ])} 34 | /> -------------------------------------------------------------------------------- /snippets/styling.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // See https://kit.svelte.dev/docs/types#app 4 | // for information about these interfaces 5 | declare namespace App { 6 | // interface Locals {} 7 | // interface Platform {} 8 | // interface Session {} 9 | // interface Stuff {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %svelte.head% 8 | 9 | 10 |
%svelte.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/Counter.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
8 | {counter} 9 |
10 | -------------------------------------------------------------------------------- /src/components/Document.svelte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohitpotato/svelte-command-palette/c8f0e297e61c2e65908350463e782e1917cf0005/src/components/Document.svelte -------------------------------------------------------------------------------- /src/components/Features.svelte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohitpotato/svelte-command-palette/c8f0e297e61c2e65908350463e782e1917cf0005/src/components/Features.svelte -------------------------------------------------------------------------------- /src/components/Hero.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |
12 |

13 | Increase your productivity exponentially. 14 |

15 |

16 | Use to bring up the command palette. 21 |

22 |
23 |
24 |

25 | Keyboard Shorcuts 26 |

27 |
28 |

29 | Press or hold to increment the counter 34 |

35 | 36 |
37 |
38 |
39 |

40 | Conditional Actions 41 |

42 |
43 |

44 | This will only run when the counter is greater than 2. 45 |

46 |
47 |
50 | {#if counter < 3} 51 |
52 | Just need {3 - counter} more 53 |
54 | {/if} 55 | {#if counter > 2} 56 |
57 | Press to run this action 62 |
63 | {/if} 64 |
65 |
66 |
67 |

68 | Advanced Conditional Actions 69 |

70 |
71 |

72 | You can run an action based on the current state of your command palette. Press to run this action 76 |

77 |
78 |
79 |
80 | You must run atleast once to enable this action 85 |
86 |
87 | Note: You can update your 88 |
89 | paletteStore 90 |
91 | from anywhere! 92 |
93 |
94 |
95 |
96 | -------------------------------------------------------------------------------- /src/components/KeyboardShortcut.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | 32 | -------------------------------------------------------------------------------- /src/components/Navbar.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | -------------------------------------------------------------------------------- /src/components/Sidebar.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 30 | -------------------------------------------------------------------------------- /src/lib/components/CommandPalette.svelte: -------------------------------------------------------------------------------- 1 | 233 | 234 | 235 | {#if isPaletteVisible} 236 |
243 |
250 |
256 |
ev.preventDefault()}> 257 | 258 | 274 |
275 | closePalette()} 276 | >{formattedEscKey} 278 |
279 |
280 | 281 |
282 |
283 |
284 | {/if} 285 |
286 | 287 | 369 | -------------------------------------------------------------------------------- /src/lib/components/KeyboardButton.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 26 | 27 | 38 | -------------------------------------------------------------------------------- /src/lib/components/Portal.svelte: -------------------------------------------------------------------------------- 1 | 41 | 42 |
43 | 44 |
45 | -------------------------------------------------------------------------------- /src/lib/components/Result.svelte: -------------------------------------------------------------------------------- 1 | 74 | 75 |
  • 88 |
    89 |

    {action.title}

    90 |

    {action.subTitle}

    91 |

    92 | {action.description || ''} 93 |

    94 |
    95 |
    96 | {#each formattedShortcut as shortcut} 97 | 98 | 99 | {shortcut} 100 | 101 | 102 | {/each} 103 |
    104 |
  • 105 | 106 | 138 | -------------------------------------------------------------------------------- /src/lib/components/ResultPanel.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | {#if actions.length > 0} 23 |
      29 | {#each actions as action (action.actionId)} 30 | 31 | {/each} 32 |
    33 | {:else} 34 |
    35 | No results found 36 |
    37 | {/if} 38 | 39 | 54 | -------------------------------------------------------------------------------- /src/lib/components/index.js: -------------------------------------------------------------------------------- 1 | import Portal from './Portal.svelte'; 2 | import CommandPalette from './CommandPalette.svelte'; 3 | import { defineActions } from '../utils'; 4 | 5 | export { Portal, CommandPalette, defineActions }; 6 | -------------------------------------------------------------------------------- /src/lib/constants/index.js: -------------------------------------------------------------------------------- 1 | const defaultAppState = { 2 | isVisible: false, 3 | textInput: '', 4 | activeCommandId: null, 5 | selectedCommandId: null, 6 | results: [] 7 | }; 8 | 9 | const THEME_CONTEXT = 'themeContext'; 10 | 11 | export { defaultAppState, THEME_CONTEXT }; 12 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | import Portal from './components/Portal.svelte'; 2 | import CommandPalette from './components/CommandPalette.svelte'; 3 | import { defineActions } from './utils'; 4 | import { paletteStore } from './store/PaletteStore'; 5 | import createStoreMethods from './utils/createStoreMethods'; 6 | 7 | export default CommandPalette; 8 | export { Portal, defineActions, paletteStore, createStoreMethods }; 9 | -------------------------------------------------------------------------------- /src/lib/store/PaletteStore.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { writable } from 'svelte/store'; 3 | import type { storeParams } from '$lib/types'; 4 | 5 | export const paletteStore = writable({ 6 | isVisible: false, 7 | textInput: '', 8 | commands: [], 9 | storeMethods: {}, 10 | actionMap: {}, 11 | activeCommandId: 0, 12 | selectedCommandId: 0, 13 | calledActions: [], 14 | results: [] 15 | }); 16 | -------------------------------------------------------------------------------- /src/lib/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type createActionMap from '$lib/utils/createActionMap'; 2 | import type createStoreMethods from '$lib/utils/createStoreMethods'; 3 | 4 | export type ActionId = number | string | null; 5 | export type onRunParams = { 6 | action: action; 7 | storeProps: storeParams; 8 | storeMethods: ReturnType; 9 | }; 10 | 11 | export type action = { 12 | actionId?: ActionId; 13 | canActionRun?: (args: onRunParams) => boolean; 14 | title: string; 15 | description?: string; 16 | subTitle?: string; 17 | onRun?: (args: onRunParams) => void; 18 | keywords?: Array; 19 | shortcut?: string; 20 | }; 21 | export type commands = Array; 22 | 23 | export interface storeParams { 24 | isVisible: boolean; 25 | textInput: string; 26 | commands: commands; 27 | storeMethods: ReturnType; 28 | actionMap: ReturnType; 29 | activeCommandId: ActionId; 30 | selectedCommandId: ActionId; 31 | calledActions: Array; 32 | results: commands; 33 | [key: string]: any; 34 | } 35 | 36 | export interface actionMap { 37 | [key: ActionId]: action; 38 | } 39 | 40 | export interface shortCutMap { 41 | [key: string]: (event: KeyboardEvent) => void; 42 | } 43 | 44 | export type className = string | null; 45 | export type cssStyle = string | null; 46 | export interface themeContext { 47 | inputClass: className; 48 | overlayClass: className; 49 | paletteWrapperInnerClass: className; 50 | resultsContainerClass: className; 51 | resultContainerClass: className; 52 | optionSelectedClass: className; 53 | titleClass: className; 54 | subtitleClass: className; 55 | descriptionClass: className; 56 | keyboardButtonClass: className; 57 | unstyled: boolean; 58 | inputStyle; 59 | overlayStyle: cssStyle; 60 | paletteWrapperInnerStyle: cssStyle; 61 | resultsContainerStyle: cssStyle; 62 | resultContainerStyle: cssStyle; 63 | optionSelectedStyle: cssStyle; 64 | titleStyle: cssStyle; 65 | subtitleStyle: cssStyle; 66 | descriptionStyle: cssStyle; 67 | keyboardButtonStyle: cssStyle; 68 | } 69 | -------------------------------------------------------------------------------- /src/lib/utils/createActionMap.ts: -------------------------------------------------------------------------------- 1 | import type { commands, actionMap } from '$lib/types'; 2 | 3 | const createActionMap = (commands: commands = []) => { 4 | return commands.reduce((acc: actionMap, curr) => { 5 | const { actionId = '' } = curr; 6 | acc[actionId] = curr; 7 | return acc; 8 | }, {}); 9 | }; 10 | 11 | export default createActionMap; 12 | -------------------------------------------------------------------------------- /src/lib/utils/createShortcuts.ts: -------------------------------------------------------------------------------- 1 | import { get } from 'svelte/store'; 2 | import { runAction } from '.'; 3 | import { paletteStore } from '../store/PaletteStore'; 4 | import type { commands, shortCutMap, storeParams } from '$lib/types'; 5 | 6 | const createShortcuts = ({ actions = [] }: { actions: commands }) => { 7 | return actions.reduce((acc: shortCutMap, curr) => { 8 | const actionHandler = (eventHandler: KeyboardEvent) => { 9 | const storeProps: storeParams = get(paletteStore); 10 | const { isVisible } = storeProps; 11 | if (!isVisible) { 12 | eventHandler.preventDefault(); 13 | runAction({ action: curr }); 14 | } 15 | }; 16 | const { shortcut } = curr; 17 | if (shortcut) { 18 | acc[shortcut] = actionHandler; 19 | } 20 | return acc; 21 | }, {}); 22 | }; 23 | 24 | export default createShortcuts; 25 | -------------------------------------------------------------------------------- /src/lib/utils/createStoreMethods.ts: -------------------------------------------------------------------------------- 1 | import { get } from 'svelte/store'; 2 | import { paletteStore } from '../store/PaletteStore'; 3 | import { defaultAppState } from '../constants'; 4 | import type { ActionId, storeParams } from '$lib/types'; 5 | 6 | const createStoreMethods = () => { 7 | const storeProps: storeParams = get(paletteStore); 8 | 9 | const resetPaletteStore = () => { 10 | paletteStore.update((n) => ({ ...n, ...defaultAppState })); 11 | }; 12 | 13 | const openPalette = () => { 14 | paletteStore.update((n) => ({ ...n, isVisible: true })); 15 | }; 16 | 17 | const closePalette = () => { 18 | resetPaletteStore(); 19 | }; 20 | 21 | const togglePalette = () => { 22 | paletteStore.update((n) => ({ 23 | ...n, 24 | isVisible: !n.isVisible, 25 | activeCommandId: '' 26 | })); 27 | }; 28 | 29 | const getAllCalledActions = () => { 30 | return storeProps.calledActions || []; 31 | }; 32 | 33 | const storeCalledAction = (id: ActionId) => { 34 | const { calledActions } = storeProps; 35 | calledActions.push(id); 36 | paletteStore.update((n) => ({ ...n, calledActions })); 37 | }; 38 | 39 | const revertLastAction = (id: ActionId) => { 40 | const { calledActions } = storeProps; 41 | calledActions.pop(); 42 | paletteStore.update((n) => ({ ...n, calledActions })); 43 | }; 44 | 45 | const resetActionLog = () => { 46 | paletteStore.update((n) => ({ ...n, calledActions: [] })); 47 | }; 48 | 49 | return { 50 | togglePalette, 51 | getAllCalledActions, 52 | storeCalledAction, 53 | revertLastAction, 54 | resetActionLog, 55 | openPalette, 56 | closePalette, 57 | resetPaletteStore 58 | }; 59 | }; 60 | 61 | export default createStoreMethods; 62 | -------------------------------------------------------------------------------- /src/lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | import Fuse from 'fuse.js'; 2 | import { paletteStore } from '../store/PaletteStore'; 3 | import { get } from 'svelte/store'; 4 | import type { action, ActionId, commands } from '../types'; 5 | import type { Properties } from 'csstype'; 6 | 7 | const noop = () => true; 8 | 9 | const defineActions = (actions: Array = []): commands => { 10 | return actions.map( 11 | ({ 12 | actionId = Math.random(), 13 | canActionRun = noop, 14 | title = '', 15 | subTitle = '', 16 | onRun = noop, 17 | description, 18 | keywords = [], 19 | shortcut = '' 20 | }) => { 21 | return { 22 | actionId, 23 | canActionRun, 24 | title, 25 | subTitle, 26 | onRun, 27 | description, 28 | keywords, 29 | shortcut 30 | }; 31 | } 32 | ); 33 | }; 34 | 35 | const formatResults = (results: Array<{ item: action }>) => 36 | results.map(({ item }: { item: action }) => item); 37 | 38 | const updatePaletteStoreAfterActionExec = (actionId: ActionId) => { 39 | paletteStore.update((n) => { 40 | return { 41 | ...n, 42 | isVisible: false, 43 | textInput: '', 44 | activeCommandId: null, 45 | selectedCommandId: null, 46 | results: [], 47 | calledActions: [...n.calledActions, actionId] 48 | }; 49 | }); 50 | }; 51 | 52 | const runAction = ({ action }: { action: action }) => { 53 | const { onRun, canActionRun = noop, actionId = '' } = action || {}; 54 | const storeProps = get(paletteStore); 55 | const { storeMethods } = storeProps; 56 | if (canActionRun({ action, storeProps, storeMethods }) && onRun) { 57 | updatePaletteStoreAfterActionExec(actionId); 58 | onRun?.({ action, storeProps, storeMethods }); 59 | return true; 60 | } 61 | paletteStore.update((n) => { 62 | return { 63 | ...n, 64 | isVisible: false 65 | }; 66 | }); 67 | return false; 68 | }; 69 | 70 | const createFuse = (actions: commands) => 71 | new Fuse(actions, { 72 | keys: [ 73 | { 74 | name: 'title', 75 | weight: 1 76 | }, 77 | { 78 | name: 'subtitle', 79 | weight: 0.7 80 | }, 81 | { 82 | name: 'description', 83 | weight: 0.6 84 | }, 85 | { 86 | name: 'keywords', 87 | weight: 0.5 88 | } 89 | ] 90 | }); 91 | 92 | const getNonEmptyArray = (...args: Array) => { 93 | return args.find((array = []) => array.length > 0) || []; 94 | }; 95 | 96 | const camelCaseToDash = (str: string) => str.replace(/([a-zA-Z])(?=[A-Z])/g, '$1-').toLowerCase(); 97 | 98 | const toCssString = (props: Properties = {}) => 99 | props 100 | ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment 101 | // @ts-ignore 102 | Object.keys(props).reduce((str, key) => `${str}; ${camelCaseToDash(key)}: ${props[key]}`, '') 103 | : ''; 104 | 105 | export { 106 | noop, 107 | defineActions, 108 | formatResults, 109 | runAction, 110 | createFuse, 111 | updatePaletteStoreAfterActionExec, 112 | getNonEmptyArray, 113 | camelCaseToDash, 114 | toCssString 115 | }; 116 | -------------------------------------------------------------------------------- /src/routes/__layout-doc.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/routes/__layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/routes/code/code.json.js: -------------------------------------------------------------------------------- 1 | export async function get({ url }) { 2 | const file = url.searchParams.get('file'); 3 | const allFiles = import.meta.glob('./*.html', { as: 'raw' }); 4 | const fileToGet = Object.keys(allFiles).find((name) => { 5 | const fileName = name.split('/').pop(); 6 | return fileName === file; 7 | }); 8 | return { 9 | body: { 10 | html: allFiles[fileToGet] 11 | }, 12 | headers: { 13 | 'Content-type': 'application/json' 14 | } 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/routes/code/command-palette.html: -------------------------------------------------------------------------------- 1 |
    <script>
     2 | 	import CommandPalette, { defineActions, createStoreMethods } from 'svelte-command-palette';
     3 | 	{/* Render command palette at the root of your application */}
     4 | 
     5 | 	// define actions using the defineActions API
     6 | 
     7 |     const paletteMethods = createStoreMethods();
     8 | 
     9 | 	<button on:click={() => paletteMethods.openPalette()}>Open Command Palette</button>
    10 | 
    11 | 	<CommandPalette
    12 | 
    13 | 		actions={defineActions([
    14 | 			{
    15 | 				// Unique identifier for your action
    16 | 				id: '1',
    17 | 				// title for you action
    18 | 				title: 'Go to github',
    19 | 				// subtitle for the action
    20 | 				subTitle: 'Press to redirect to github',
    21 | 				// description of the action
    22 | 				description: '1. something going here idk about?',
    23 | 				// called when the action is triggered along with palette state and methods
    24 | 				onRun: ({ action, storeProps, storeMethods }) => {
    25 | 					console.log('do something');
    26 | 				},
    27 | 				// decide whether to run the action based on your palette state
    28 | 				canActionRun: ({ action, storeProps, storeMethods }) => {
    29 | 					return true;
    30 | 				},
    31 | 				// keyboard shortcut to call the action
    32 | 				shortcut: 'G G',
    33 | 				// allows searching for actions via keywords
    34 | 				keywords: ['git', 'github']
    35 | 			}
    36 | 		])}
    37 | 	/>
    38 | </script>
    39 | 
    -------------------------------------------------------------------------------- /src/routes/code/paletteStore.html: -------------------------------------------------------------------------------- 1 |
    import { paletteStore } from 'svelte-command-palette';
     2 | 
     3 | // Update your paletteStore from anywhere in the application
     4 | 
     5 | paletteStore.update((storeValue) => {
     6 |     return {
     7 |         ...storeValue,
     8 |         customValue: 'new value!'
     9 |     }
    10 | })
    11 | 
    12 | // You can use this custom value in your onRun / canActionRun
    13 | 
    14 | <CommandPalette
    15 |     actions={defineActions([
    16 |         {
    17 |             // ..............................
    18 | 
    19 |             // called when the action is triggered along with palette state and methods
    20 |             onRun: ({ action, storeProps, storeMethods }) => {
    21 |                 console.log(storeProps.customValue)
    22 |                 // new value!
    23 |             },
    24 |             // decide whether to run the action based on your palette state
    25 |             canActionRun: ({ action, storeProps, storeMethods }) => {
    26 |                 if(storeProps.customValue === 'old value') {
    27 |                     return false
    28 |                 }
    29 |                 return true
    30 |             },
    31 | 
    32 |         }
    33 |     ])}
    34 | />
    -------------------------------------------------------------------------------- /src/routes/code/styling.html: -------------------------------------------------------------------------------- 1 |
    <CommandPalette
     2 | 	unstyled={false}
     3 | 	placeholder={paletteTheme.placeholder}
     4 | 	commands={actions}
     5 | 	keyboardButtonClass="bg-red-500"
     6 | 	inputClass="bg-blue-200"
     7 | 	overlayClass="bg-gray-200"
     8 | 	paletteWrapperInnerClass="w-full"
     9 | 	resultsContainerClass="h-max"
    10 | 	resultContainerClass="bg-black"
    11 | 	optionSelectedClass="text-blue-200"
    12 | 	subtitleClass="text-red-200"
    13 | 	titleClass="text-red-500"
    14 | 	descriptionClass=""
    15 | 	overlayStyle={{ width: "200px", height: '500px' }}
    16 | 	paletteWrapperInnerStyle={{ width: "200px", height: '500px' }}
    17 | 	inputStyle={paletteTheme.inputStyles}
    18 | 	resultsContainerStyle={paletteTheme.resultsContainerStyle}
    19 | 	resultContainerStyle={paletteTheme.resultContainerStyle}
    20 | 	titleStyle={paletteTheme.titleStyle}
    21 | 	descriptionStyle={paletteTheme.descriptionStyle}
    22 | 	subtitleStyle={paletteTheme.subtitleStyle}
    23 | 	optionSelectedStyle={currentTheme === 'light'
    24 | 		? { background: 'skyblue' }
    25 | 		: { background: 'blue' }}
    26 | 	keyboardButtonStyle={{}}
    27 | />
    -------------------------------------------------------------------------------- /src/routes/docs/__layout@doc.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 |
    8 | 9 |
    10 |
    11 | -------------------------------------------------------------------------------- /src/routes/docs/command-palette-api.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 17 | 18 |
    19 |

    Command Palette API

    20 |

    21 | Actions can be defined with the defineActions API. 22 |

    23 | 24 |

    These actions can then be passed to CommandPalette

    25 | 26 |

    Render CommandPalette at the root of your application.

    27 |
    28 | 29 |
    {@html html}
    30 | 31 | 37 | -------------------------------------------------------------------------------- /src/routes/docs/define-actions.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/docs/index.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
    10 |
    11 |
    12 | -------------------------------------------------------------------------------- /src/routes/docs/palette-store.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 |
    17 |

    Palette Store

    18 |

    19 | Palette store is a normal svelte store, you can update or subscribe to paletteStore like any svelte store. 20 |

    21 |
    22 | 23 |
    {@html html}
    24 | 25 | 31 | -------------------------------------------------------------------------------- /src/routes/docs/styling.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 | 17 |
    18 |

    Styling

    19 |

    20 | You have complete control over the styling of the command palette 21 |

    22 | 23 |

    24 | You can use classes (tailwind too) to style the command palette. Note that svelte applies the 25 | scoped classes with priority, to overwrite this, pass
    unstyled={true}
    26 |

    27 | 28 |

    29 | Or just directly pass css styles to completely change the look and feel of the command palette 30 |

    31 |
    32 | 33 |
    {@html html}
    34 | 35 | 41 | -------------------------------------------------------------------------------- /src/routes/index.svelte: -------------------------------------------------------------------------------- 1 | 159 | 160 | 175 |
    176 | 177 |
    178 | 179 | 186 | -------------------------------------------------------------------------------- /src/store/themeStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | type Theme = string | null; 4 | 5 | const themeStore = writable(null); 6 | 7 | export default themeStore; 8 | -------------------------------------------------------------------------------- /src/utils/switchTheme.js: -------------------------------------------------------------------------------- 1 | import themeStore from '../store/themeStore'; 2 | const LIGHT = 'light'; 3 | const DARK = 'dark'; 4 | 5 | const handleThemeSwitch = () => { 6 | const theme = localStorage.getItem('theme') || 'light'; 7 | const root = window.document.documentElement; 8 | const isDark = theme === DARK; 9 | root.classList.remove(theme); 10 | root.classList.add(isDark ? LIGHT : DARK); 11 | localStorage.setItem('theme', isDark ? LIGHT : DARK); 12 | themeStore.set(isDark ? LIGHT : DARK); 13 | }; 14 | 15 | export default handleThemeSwitch; 16 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohitpotato/svelte-command-palette/c8f0e297e61c2e65908350463e782e1917cf0005/static/favicon.png -------------------------------------------------------------------------------- /static/tailwind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohitpotato/svelte-command-palette/c8f0e297e61c2e65908350463e782e1917cf0005/static/tailwind.png -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: preprocess({ 9 | postcss: true 10 | }), 11 | 12 | kit: { 13 | adapter: adapter(), 14 | vite: { 15 | serve: { 16 | fs: { 17 | allow: ['./package'] 18 | } 19 | } 20 | } 21 | } 22 | }; 23 | 24 | export default config; 25 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: 'class', 4 | content: ['./src/**/*.{html,js,svelte,ts}'], 5 | theme: { 6 | extend: { 7 | colors: { 8 | 'svelte-brand': '#e44c1c', 9 | 'dark-mode-black': '#121212', 10 | 'light-gray': '#ECEDF3', 11 | 'dark-mode-gray': '#212121', 12 | 'dark-text-gray': '#313654' 13 | } 14 | } 15 | }, 16 | plugins: [] 17 | }; 18 | -------------------------------------------------------------------------------- /tests/test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test('index page has expected h1', async ({ page }) => { 4 | await page.goto('/'); 5 | expect(await page.textContent('h1')).toBe('Welcome to SvelteKit'); 6 | }); 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "lib": ["dom"], 6 | "checkJs": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "sourceMap": true, 12 | "strict": true 13 | } 14 | } 15 | --------------------------------------------------------------------------------