├── .changeset ├── README.md └── config.json ├── .eslintignore ├── .eslintrc.cjs ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── playwright.config.ts ├── pnpm-lock.yaml ├── postcss.config.cjs ├── src ├── app.d.ts ├── app.html ├── docs │ ├── code.ts │ ├── components │ │ ├── cmdk-wrapper.svelte │ │ ├── cmdk │ │ │ ├── framer │ │ │ │ ├── framer-cmdk.svelte │ │ │ │ └── icons │ │ │ │ │ ├── avatar.svelte │ │ │ │ │ ├── badge.svelte │ │ │ │ │ ├── button.svelte │ │ │ │ │ ├── container.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── input.svelte │ │ │ │ │ ├── radio.svelte │ │ │ │ │ ├── search.svelte │ │ │ │ │ └── slider.svelte │ │ │ ├── index.ts │ │ │ ├── linear │ │ │ │ ├── icons │ │ │ │ │ ├── assign-to-me.svelte │ │ │ │ │ ├── assign-to.svelte │ │ │ │ │ ├── change-labels.svelte │ │ │ │ │ ├── change-priority.svelte │ │ │ │ │ ├── change-status.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── remove-label.svelte │ │ │ │ │ └── set-due-date.svelte │ │ │ │ └── linear-cmdk.svelte │ │ │ ├── raycast │ │ │ │ ├── icons │ │ │ │ │ ├── clipboard.svelte │ │ │ │ │ ├── finder.svelte │ │ │ │ │ ├── hammer.svelte │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── raycast-dark.svelte │ │ │ │ │ ├── raycast-light.svelte │ │ │ │ │ ├── star.svelte │ │ │ │ │ └── window.svelte │ │ │ │ ├── item.svelte │ │ │ │ ├── raycast-cmdk.svelte │ │ │ │ ├── sub-command.svelte │ │ │ │ └── sub-item.svelte │ │ │ └── vercel │ │ │ │ ├── home.svelte │ │ │ │ ├── icons │ │ │ │ ├── contact.svelte │ │ │ │ ├── docs.svelte │ │ │ │ ├── feedback.svelte │ │ │ │ ├── index.ts │ │ │ │ ├── plus.svelte │ │ │ │ ├── projects.svelte │ │ │ │ └── teams.svelte │ │ │ │ ├── item.svelte │ │ │ │ ├── projects.svelte │ │ │ │ └── vercel-cmdk.svelte │ │ ├── code-block.svelte │ │ ├── footer.svelte │ │ ├── github-button.svelte │ │ ├── icons │ │ │ ├── copied.svelte │ │ │ ├── copy.svelte │ │ │ ├── figma.svelte │ │ │ ├── framer.svelte │ │ │ ├── github.svelte │ │ │ ├── index.ts │ │ │ ├── linear.svelte │ │ │ ├── raycast.svelte │ │ │ ├── slack.svelte │ │ │ ├── vercel.svelte │ │ │ └── youtube.svelte │ │ ├── index.ts │ │ ├── install-button.svelte │ │ ├── logo.svelte │ │ ├── theme-switcher.svelte │ │ └── version-badge.svelte │ ├── copy-code.ts │ ├── highlight.ts │ ├── types.ts │ └── utils.ts ├── index.test.ts ├── lib │ ├── cmdk │ │ ├── command.ts │ │ ├── components │ │ │ ├── Command.svelte │ │ │ ├── CommandDialog.svelte │ │ │ ├── CommandEmpty.svelte │ │ │ ├── CommandGroup.svelte │ │ │ ├── CommandInput.svelte │ │ │ ├── CommandItem.svelte │ │ │ ├── CommandList.svelte │ │ │ ├── CommandLoading.svelte │ │ │ └── CommandSeparator.svelte │ │ ├── index.ts │ │ └── types.ts │ ├── index.ts │ ├── internal │ │ ├── command-score.ts │ │ ├── helpers │ │ │ ├── callbacks.ts │ │ │ ├── event.ts │ │ │ ├── id.ts │ │ │ ├── index.ts │ │ │ ├── is.ts │ │ │ ├── kbd.ts │ │ │ ├── object.ts │ │ │ ├── sleep.ts │ │ │ ├── store.ts │ │ │ └── style.ts │ │ ├── index.ts │ │ └── types.ts │ └── types.ts ├── routes │ ├── +layout.svelte │ ├── +layout.ts │ ├── +page.svelte │ └── sink │ │ └── +page.svelte └── styles │ ├── app.postcss │ ├── cmdk │ ├── framer.postcss │ ├── linear.postcss │ ├── raycast.postcss │ └── vercel.postcss │ ├── code.postcss │ ├── globals.postcss │ └── icons.postcss ├── static ├── favicon.svg ├── grid.svg ├── huntabyte.png ├── inter-var-latin.woff2 ├── line.svg ├── og.png ├── paco.png ├── rauno.jpeg ├── robots.txt └── vercel.svg ├── svelte.config.js ├── tests └── test.ts ├── tsconfig.json └── vite.config.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": ["@svitejs/changesets-changelog-github-compact", { "repo": "huntabyte/cmdk-sv" }], 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | /dist 15 | .changeset/ -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type { import("eslint").Linter.Config } */ 2 | module.exports = { 3 | root: true, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:svelte/recommended', 8 | 'prettier' 9 | ], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['@typescript-eslint'], 12 | parserOptions: { 13 | sourceType: 'module', 14 | ecmaVersion: 'latest', 15 | extraFileExtensions: ['.svelte'] 16 | }, 17 | env: { 18 | browser: true, 19 | es2024: true, 20 | node: true 21 | }, 22 | globals: { $$Generic: 'readable', NodeJS: true }, 23 | rules: { 24 | 'no-console': 'warn', 25 | '@typescript-eslint/no-unused-vars': [ 26 | 'warn', 27 | { 28 | argsIgnorePattern: '^_', 29 | varsIgnorePattern: '^_' 30 | } 31 | ], 32 | 'svelte/no-target-blank': 'error', 33 | 'svelte/no-immutable-reactive-statements': 'error', 34 | 'svelte/no-reactive-literals': 'error', 35 | 'svelte/no-useless-mustaches': 'error', 36 | 'svelte/button-has-type': 'off', 37 | 'svelte/require-each-key': 'off', 38 | 'svelte/no-at-html-tags': 'off', 39 | 'svelte/no-unused-svelte-ignore': 'off', 40 | 'svelte/require-stores-init': 'off' 41 | }, 42 | overrides: [ 43 | { 44 | files: ['*.svelte'], 45 | parser: 'svelte-eslint-parser', 46 | parserOptions: { 47 | parser: '@typescript-eslint/parser' 48 | }, 49 | rules: { 50 | '@typescript-eslint/no-unused-vars': [ 51 | 'warn', 52 | { 53 | argsIgnorePattern: '^_', 54 | varsIgnorePattern: '^\\$\\$(Props|Events|Slots|Generic)$' 55 | } 56 | ] 57 | } 58 | }, 59 | { 60 | files: ['*.ts'], 61 | parser: '@typescript-eslint/parser', 62 | rules: { 63 | '@typescript-eslint/ban-types': [ 64 | 'error', 65 | { 66 | extendDefaults: true, 67 | types: { 68 | '{}': false 69 | } 70 | } 71 | ] 72 | } 73 | }, 74 | { 75 | files: ['*.js', '*.svelte', '*.ts'], 76 | rules: { 77 | 'no-console': 'error' 78 | } 79 | } 80 | ] 81 | }; 82 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [huntabyte] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: huntabyte 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.event.number || github.sha }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | check: 12 | name: Run svelte-check 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: pnpm/action-setup@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: 18 20 | cache: pnpm 21 | 22 | - name: Install dependencies 23 | run: pnpm install 24 | 25 | - name: Run svelte-check 26 | run: pnpm check 27 | 28 | lint: 29 | runs-on: ubuntu-latest 30 | name: Lint 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: pnpm/action-setup@v4 34 | - uses: actions/setup-node@v4 35 | with: 36 | node-version: 18 37 | cache: pnpm 38 | 39 | - name: Install dependencies 40 | run: pnpm install 41 | 42 | - run: pnpm lint 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | permissions: 13 | contents: write # to create release (changesets/action) 14 | pull-requests: write # to create pull request (changesets/action) 15 | name: Release 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout Repo 19 | uses: actions/checkout@v4 20 | with: 21 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 22 | fetch-depth: 0 23 | - uses: pnpm/action-setup@v4 24 | - name: Setup Node.js 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: 18 28 | cache: pnpm 29 | 30 | - run: pnpm install 31 | 32 | - name: Create Release Pull Request or Publish to npm 33 | id: changesets 34 | uses: changesets/action@v1 35 | with: 36 | publish: pnpm release 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /dist 5 | /.svelte-kit 6 | /package 7 | .env 8 | .env.* 9 | !.env.example 10 | vite.config.js.timestamp-* 11 | vite.config.ts.timestamp-* 12 | .vercel 13 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | resolution-mode=highest 3 | package-manager-strict=false -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | /dist 15 | .vercel 16 | .changeset -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # cmdk-sv 2 | 3 | ## 0.0.19 4 | 5 | ### Patch Changes 6 | 7 | - fix: escape problematic characters in selectors ([#103](https://github.com/huntabyte/cmdk-sv/pull/103)) 8 | 9 | ## 0.0.18 10 | 11 | ### Patch Changes 12 | 13 | - chore: Add Svelte 5 as a peer dependency ([#88](https://github.com/huntabyte/cmdk-sv/pull/88)) 14 | 15 | - fix: select first value after search ([#83](https://github.com/huntabyte/cmdk-sv/pull/83)) 16 | 17 | - fix: Removes self-closing non-void elements to fix Svelte 5 warnings ([#84](https://github.com/huntabyte/cmdk-sv/pull/84)) 18 | 19 | - fix: Support meta key and home/end shortcuts ([#78](https://github.com/huntabyte/cmdk-sv/pull/78)) 20 | 21 | ## 0.0.17 22 | 23 | ### Patch Changes 24 | 25 | - fix: remove leftover logs ([#70](https://github.com/huntabyte/cmdk-sv/pull/70)) 26 | 27 | ## 0.0.16 28 | 29 | ### Patch Changes 30 | 31 | - Fixed bug where page would crash when a large list was rendered ([#55](https://github.com/huntabyte/cmdk-sv/pull/55)) 32 | 33 | ## 0.0.15 34 | 35 | ### Patch Changes 36 | 37 | - chore: Updated `svelte-package` to fix malformed package build ([#64](https://github.com/huntabyte/cmdk-sv/pull/64)) 38 | 39 | ## 0.0.14 40 | 41 | ### Patch Changes 42 | 43 | - Expose keydown event from Input component ([#57](https://github.com/huntabyte/cmdk-sv/pull/57)) 44 | 45 | - change `moduleResolution` to `NodeNext` ([#62](https://github.com/huntabyte/cmdk-sv/pull/62)) 46 | 47 | ## 0.0.13 48 | 49 | ### Patch Changes 50 | 51 | - Fix type resolution and intellisense for most components ([#46](https://github.com/huntabyte/cmdk-sv/pull/46)) 52 | 53 | ## 0.0.12 54 | 55 | ### Patch Changes 56 | 57 | - feat: expose state slot prop for Command.Root ([#38](https://github.com/huntabyte/cmdk-sv/pull/38)) 58 | 59 | ## 0.0.11 60 | 61 | ### Patch Changes 62 | 63 | - fix: maintain original list order when clearing search value ([#35](https://github.com/huntabyte/cmdk-sv/pull/35)) 64 | 65 | ## 0.0.10 66 | 67 | ### Patch Changes 68 | 69 | - fix: move Command additional props to types file ([#31](https://github.com/huntabyte/cmdk-sv/pull/31)) 70 | 71 | ## 0.0.9 72 | 73 | ### Patch Changes 74 | 75 | - feat: export `defaultFilter` function ([#29](https://github.com/huntabyte/cmdk-sv/pull/29)) 76 | 77 | ## 0.0.8 78 | 79 | ### Patch Changes 80 | 81 | - fix: `autofocus` input behavior ([#25](https://github.com/huntabyte/cmdk-sv/pull/25)) 82 | 83 | ## 0.0.7 84 | 85 | ### Patch Changes 86 | 87 | - Fix: Bug with conditional rendering ([#22](https://github.com/huntabyte/cmdk-sv/pull/22)) 88 | 89 | ## 0.0.6 90 | 91 | ### Patch Changes 92 | 93 | - Fix: bug where best match wasnt selected ([#17](https://github.com/huntabyte/cmdk-sv/pull/17)) 94 | 95 | ## 0.0.5 96 | 97 | ### Patch Changes 98 | 99 | - Add `asChild` prop & forward input events ([#14](https://github.com/huntabyte/cmdk-sv/pull/14)) 100 | 101 | ## 0.0.4 102 | 103 | ### Patch Changes 104 | 105 | - Fix: input autofocus ([#11](https://github.com/huntabyte/cmdk-sv/pull/11)) 106 | 107 | ## 0.0.3 108 | 109 | ### Patch Changes 110 | 111 | - Fix: `autofocus` prop autofocuses input on mount ([#8](https://github.com/huntabyte/cmdk-sv/pull/8)) 112 | 113 | ## 0.0.2 114 | 115 | ### Patch Changes 116 | 117 | - Fix exports ([#5](https://github.com/huntabyte/cmdk-sv/pull/5)) 118 | 119 | ## 0.0.1 120 | 121 | ### Patch Changes 122 | 123 | - initial release ([#1](https://github.com/huntabyte/cmdk-sv/pull/1)) 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Hunter Johnston 4 | Copyright (c) 2022 Paco Coursey 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmdk-sv", 3 | "version": "0.0.19", 4 | "scripts": { 5 | "dev": "vite dev", 6 | "build": "vite build && npm run package", 7 | "preview": "vite preview", 8 | "package": "svelte-kit sync && svelte-package && publint", 9 | "prepublishOnly": "npm run package", 10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 12 | "lint": "prettier --check . && eslint .", 13 | "format": "prettier --write .", 14 | "test": "vitest", 15 | "release": "changeset publish", 16 | "changeset": "changeset" 17 | }, 18 | "exports": { 19 | ".": { 20 | "types": "./dist/index.d.ts", 21 | "svelte": "./dist/index.js" 22 | } 23 | }, 24 | "files": [ 25 | "dist", 26 | "!dist/**/*.test.*", 27 | "!dist/**/*.spec.*" 28 | ], 29 | "peerDependencies": { 30 | "svelte": "^4.0.0 || ^5.0.0-next.1" 31 | }, 32 | "devDependencies": { 33 | "@changesets/cli": "^2.27.7", 34 | "@playwright/test": "^1.45.1", 35 | "@sveltejs/adapter-vercel": "^4.0.0", 36 | "@sveltejs/kit": "^2.5.18", 37 | "@sveltejs/package": "^2.3.2", 38 | "@sveltejs/vite-plugin-svelte": "^3.1.1", 39 | "@svitejs/changesets-changelog-github-compact": "^1.1.0", 40 | "@types/prismjs": "^1.26.4", 41 | "@typescript-eslint/eslint-plugin": "^7.16.1", 42 | "@typescript-eslint/parser": "^7.16.1", 43 | "autoprefixer": "^10.4.19", 44 | "eslint": "^8.57.0", 45 | "eslint-config-prettier": "^8.10.0", 46 | "eslint-plugin-svelte": "^2.42.0", 47 | "mode-watcher": "^0.4.0", 48 | "postcss": "^8.4.39", 49 | "postcss-load-config": "^6.0.1", 50 | "postcss-preset-env": "^9.6.0", 51 | "prettier": "^3.3.3", 52 | "prettier-plugin-svelte": "^3.2.5", 53 | "prism-svelte": "^0.5.0", 54 | "prismjs": "^1.29.0", 55 | "publint": "^0.1.9", 56 | "svelte": "^4.2.18", 57 | "svelte-check": "^3.8.4", 58 | "tslib": "^2.6.2", 59 | "typescript": "^5.2.2", 60 | "vite": "^5.3.3", 61 | "vitest": "^1.6.0" 62 | }, 63 | "svelte": "./dist/index.js", 64 | "types": "./dist/index.d.ts", 65 | "type": "module", 66 | "dependencies": { 67 | "bits-ui": "^0.21.12", 68 | "nanoid": "^5.0.7" 69 | }, 70 | "packageManager": "pnpm@9.5.0" 71 | } 72 | -------------------------------------------------------------------------------- /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: 4173 7 | }, 8 | testDir: 'tests', 9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/ 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { autoprefixer: {} } 3 | }; 4 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | interface PageData { 8 | version: string; 9 | } 10 | // interface Platform {} 11 | } 12 | } 13 | 14 | export {}; 15 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/docs/code.ts: -------------------------------------------------------------------------------- 1 | export const code = ` 5 | 6 | 7 | 8 | 9 | {#if loading} 10 | Loading... 11 | {/if} 12 | 13 | No results found. 14 | 15 | 16 | Apple 17 | Orange 18 | 19 | Pear 20 | Blueberry 21 | 22 | 23 | Fish 24 | 25 | `; 26 | 27 | // a function that removes indentation from all lines in a string 28 | export function unindent(s: string) { 29 | const lines = s.split('\n'); 30 | const indent = lines[0].match(/^\s*/)?.[0]; 31 | if (!indent) return s; 32 | return lines.map((line) => line.replace(indent, '')).join('\n'); 33 | } 34 | 35 | // remove spaces from the beginning and end of a string 36 | export function trim(s: string) { 37 | return s.replace(/^\s+|\s+$/g, ''); 38 | } 39 | -------------------------------------------------------------------------------- /src/docs/components/cmdk-wrapper.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 |
28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/framer/framer-cmdk.svelte: -------------------------------------------------------------------------------- 1 | 62 | 63 |
64 | 65 |
66 | 67 | 68 |
69 | 70 |
71 |
72 | 73 | {#each components as { value, subtitle, icon }} 74 | 75 |
76 | 77 |
78 |
79 | {value} 80 | {subtitle} 81 |
82 |
83 | {/each} 84 |
85 |
86 |
87 |
88 | {#if value === 'Button'} 89 | 90 | {:else if value === 'Input'} 91 | 92 | {:else if value === 'Badge'} 93 |
Badge
94 | {:else if value === 'Radio'} 95 | 99 | {:else if value === 'Avatar'} 100 | Avatar of Rauno 101 | {:else if value === 'Slider'} 102 |
103 |
104 |
105 | {:else if value === 'Container'} 106 |
107 | {/if} 108 |
109 |
110 |
111 |
112 |
113 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/framer/icons/avatar.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/framer/icons/badge.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/framer/icons/button.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/framer/icons/container.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/framer/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AvatarIcon } from './avatar.svelte'; 2 | export { default as BadgeIcon } from './badge.svelte'; 3 | export { default as ButtonIcon } from './button.svelte'; 4 | export { default as ContainerIcon } from './container.svelte'; 5 | export { default as InputIcon } from './input.svelte'; 6 | export { default as RadioIcon } from './radio.svelte'; 7 | export { default as SearchIcon } from './search.svelte'; 8 | export { default as SliderIcon } from './slider.svelte'; 9 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/framer/icons/input.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/framer/icons/radio.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/framer/icons/search.svelte: -------------------------------------------------------------------------------- 1 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/framer/icons/slider.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/index.ts: -------------------------------------------------------------------------------- 1 | export { default as RaycastCMDK } from './raycast/raycast-cmdk.svelte'; 2 | export { default as LinearCMDK } from './linear/linear-cmdk.svelte'; 3 | export { default as VercelCMDK } from './vercel/vercel-cmdk.svelte'; 4 | export { default as FramerCMDK } from './framer/framer-cmdk.svelte'; 5 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/linear/icons/assign-to-me.svelte: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/linear/icons/assign-to.svelte: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/linear/icons/change-labels.svelte: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/linear/icons/change-priority.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/linear/icons/change-status.svelte: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/linear/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AssignToIcon } from './assign-to.svelte'; 2 | export { default as AssignToMeIcon } from './assign-to-me.svelte'; 3 | export { default as ChangeLabelsIcon } from './change-labels.svelte'; 4 | export { default as ChangePriorityIcon } from './change-priority.svelte'; 5 | export { default as ChangeStatusIcon } from './change-status.svelte'; 6 | export { default as RemoveLabelIcon } from './remove-label.svelte'; 7 | export { default as SetDueDateIcon } from './set-due-date.svelte'; 8 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/linear/icons/remove-label.svelte: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/linear/icons/set-due-date.svelte: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/linear/linear-cmdk.svelte: -------------------------------------------------------------------------------- 1 | 59 | 60 |
61 | 62 |
Issue - FUN-343
63 | 64 | 65 | No results found. 66 | {#each items as { label, shortcut, icon }} 67 | 68 | 69 | {label} 70 |
71 | {#each shortcut as key} 72 | {key} 73 | {/each} 74 |
75 |
76 | {/each} 77 |
78 |
79 |
80 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/icons/clipboard.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/icons/finder.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/icons/hammer.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ClipboardIcon } from './clipboard.svelte'; 2 | export { default as FinderIcon } from './finder.svelte'; 3 | export { default as HammerIcon } from './hammer.svelte'; 4 | export { default as RaycastDarkIcon } from './raycast-dark.svelte'; 5 | export { default as RaycastLightIcon } from './raycast-light.svelte'; 6 | export { default as StarIcon } from './star.svelte'; 7 | export { default as WindowIcon } from './window.svelte'; 8 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/icons/raycast-dark.svelte: -------------------------------------------------------------------------------- 1 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/icons/raycast-light.svelte: -------------------------------------------------------------------------------- 1 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/icons/star.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/icons/window.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/item.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | {#if isCommand} 14 | Command 15 | {:else} 16 | Application 17 | {/if} 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/raycast-cmdk.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |
23 | 24 |
25 | 26 |
27 | 28 | No results found. 29 | 30 | 31 | 32 | 33 | 34 | Linear 35 | 36 | 37 | 38 | 39 | 40 | Figma 41 | 42 | 43 | 44 | 45 | 46 | Slack 47 | 48 | 49 | 50 | 51 | 52 | YouTube 53 | 54 | 55 | 56 | 57 | 58 | Raycast 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | Clipboard History 67 | 68 | 69 | 70 | 71 | 72 | Import Extension 73 | 74 | 75 | 76 | 77 | 78 | Manage Extensions 79 | 80 | 81 | 82 | 83 |
84 | {#if $mode === 'dark'} 85 | 86 | {:else} 87 | 88 | {/if} 89 | 93 | 94 |
95 | 96 | 97 |
98 |
99 |
100 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/sub-command.svelte: -------------------------------------------------------------------------------- 1 | 44 | 45 | 46 | 47 | 57 | 58 | {#if open} 59 | 60 | 61 | 62 | 63 | 64 | 65 | Open Application 66 | 67 | 68 | 69 | Show in Finder 70 | 71 | 72 | 73 | Show Info in Finder 74 | 75 | 76 | 77 | Add to Favorites 78 | 79 | 80 | 81 | 82 | 83 | 84 | {/if} 85 | 86 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/raycast/sub-item.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 |
9 | {#each shortcut.split(' ') as key, i (i)} 10 | {key} 11 | {/each} 12 |
13 |
14 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/home.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | Search Projects... 19 | 20 | 21 | 22 | Create New Project... 23 | 24 | 25 | 26 | 27 | 28 | Search Teams... 29 | 30 | 31 | 32 | Create New Team... 33 | 34 | 35 | 36 | 37 | 38 | Search Docs... 39 | 40 | 41 | 42 | Send Feedback... 43 | 44 | 45 | 46 | Contact Support 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/icons/contact.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/icons/docs.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/icons/feedback.svelte: -------------------------------------------------------------------------------- 1 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ContactIcon } from './contact.svelte'; 2 | export { default as DocsIcon } from './docs.svelte'; 3 | export { default as FeedbackIcon } from './feedback.svelte'; 4 | export { default as PlusIcon } from './plus.svelte'; 5 | export { default as ProjectsIcon } from './projects.svelte'; 6 | export { default as TeamsIcon } from './teams.svelte'; 7 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/icons/plus.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/icons/projects.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/icons/teams.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/item.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | {#if shortcut} 11 |
12 | {#each shortcut.split(' ') as key} 13 | {key} 14 | {/each} 15 |
16 | {/if} 17 |
18 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/projects.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | {#each { length: 6 } as _, i} 6 | 7 | Project {i + 1} 8 | 9 | {/each} 10 | -------------------------------------------------------------------------------- /src/docs/components/cmdk/vercel/vercel-cmdk.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 |
50 | 51 |
52 | {#each pages as page} 53 |
54 | {page} 55 |
56 | {/each} 57 |
58 | 59 | 60 | No results found. 61 | {#if activePage === 'home'} 62 | { 64 | pages = [...pages, 'projects']; 65 | }} 66 | /> 67 | {/if} 68 | {#if activePage === 'projects'} 69 | 70 | {/if} 71 | 72 |
73 |
74 | -------------------------------------------------------------------------------- /src/docs/components/code-block.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 |
39 |
40 |
41 |
46 | 		
49 | 		
50 | {@html rawCodeString} 51 |
52 |
53 | -------------------------------------------------------------------------------- /src/docs/components/footer.svelte: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /src/docs/components/github-button.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | huntabyte/cmdk-sv 13 | 14 | -------------------------------------------------------------------------------- /src/docs/components/icons/copied.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/docs/components/icons/copy.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /src/docs/components/icons/figma.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/docs/components/icons/framer.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/docs/components/icons/github.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/docs/components/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CopiedIcon } from './copied.svelte'; 2 | export { default as CopyIcon } from './copy.svelte'; 3 | export { default as FigmaIcon } from './figma.svelte'; 4 | export { default as FramerIcon } from './framer.svelte'; 5 | export { default as GitHubIcon } from './github.svelte'; 6 | export { default as LinearIcon } from './linear.svelte'; 7 | export { default as RaycastIcon } from './raycast.svelte'; 8 | export { default as SlackIcon } from './slack.svelte'; 9 | export { default as VercelIcon } from './vercel.svelte'; 10 | export { default as YouTubeIcon } from './youtube.svelte'; 11 | -------------------------------------------------------------------------------- /src/docs/components/icons/linear.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 11 | 15 | 19 | 23 | 24 | -------------------------------------------------------------------------------- /src/docs/components/icons/raycast.svelte: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/docs/components/icons/slack.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 14 | 18 | 22 | 26 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /src/docs/components/icons/vercel.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/docs/components/icons/youtube.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/docs/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as VersionBadge } from './version-badge.svelte'; 2 | export { default as GitHubButton } from './github-button.svelte'; 3 | export { default as InstallButton } from './install-button.svelte'; 4 | export { default as CMDKWrapper } from './cmdk-wrapper.svelte'; 5 | export { default as ThemeSwitcher } from './theme-switcher.svelte'; 6 | export { default as CodeBlock } from './code-block.svelte'; 7 | export { default as Footer } from './footer.svelte'; 8 | -------------------------------------------------------------------------------- /src/docs/components/install-button.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | -------------------------------------------------------------------------------- /src/docs/components/logo.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /src/docs/components/theme-switcher.svelte: -------------------------------------------------------------------------------- 1 | 85 | 86 |
87 | 88 | ← 89 | 90 | {#each themes as { key, icon }} 91 | {@const isActive = theme === key} 92 | 99 | {/each} 100 | 102 |
103 | -------------------------------------------------------------------------------- /src/docs/components/version-badge.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | {$page.data.version} 7 | 8 | -------------------------------------------------------------------------------- /src/docs/copy-code.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import { isBrowser } from '$lib/internal/index.js'; 3 | 4 | export function createCopyCodeButton() { 5 | let codeString = ''; 6 | const copied = writable(false); 7 | let copyTimeout = 0; 8 | 9 | function copyCode() { 10 | if (!isBrowser) return; 11 | navigator.clipboard.writeText(codeString); 12 | copied.set(true); 13 | clearTimeout(copyTimeout); 14 | copyTimeout = window.setTimeout(() => { 15 | copied.set(false); 16 | }, 2500); 17 | } 18 | 19 | function setCodeString(node: HTMLElement) { 20 | codeString = node.innerText.trim() ?? ''; 21 | } 22 | 23 | return { 24 | copied: copied, 25 | copyCode: copyCode, 26 | setCodeString: setCodeString 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/docs/highlight.ts: -------------------------------------------------------------------------------- 1 | import type { EnvConfig, Token } from './types.js'; 2 | import type { Grammar, Token as PrismToken, TokenStream } from 'prismjs'; 3 | import Prism from 'prismjs'; 4 | 5 | const newlineRe = /\r\n|\r|\n/; 6 | 7 | // Empty lines need to contain a single empty token, denoted with { empty: true } 8 | function normalizeEmptyLines(line: Token[]) { 9 | if (line.length === 0) { 10 | line.push({ 11 | types: ['plain'], 12 | content: '\n', 13 | empty: true 14 | }); 15 | } else if (line.length === 1 && line[0].content === '') { 16 | line[0].content = '\n'; 17 | line[0].empty = true; 18 | } 19 | } 20 | 21 | function appendTypes(types: string[], add: string[] | string): string[] { 22 | const typesSize = types.length; 23 | 24 | if (typesSize > 0 && types[typesSize - 1] === add) { 25 | return types; 26 | } 27 | 28 | return types.concat(add); 29 | } 30 | 31 | // Takes an array of Prism's tokens and groups them by line, turning plain 32 | // strings into tokens as well. Tokens can become recursive in some cases, 33 | // which means that their types are concatenated. Plain-string tokens however 34 | // are always of type "plain". 35 | // This is not recursive to avoid exceeding the call-stack limit, since it's unclear 36 | // how nested Prism's tokens can become 37 | export function normalizeTokens(tokens: (PrismToken | string)[]): Token[][] { 38 | const typeArrStack: string[][] = [[]]; 39 | const tokenArrStack = [tokens]; 40 | const tokenArrIndexStack = [0]; 41 | const tokenArrSizeStack = [tokens.length]; 42 | let i = 0; 43 | let stackIndex = 0; 44 | let currentLine: Token[] = []; 45 | const acc = [currentLine]; 46 | 47 | while (stackIndex > -1) { 48 | while ((i = tokenArrIndexStack[stackIndex]++) < tokenArrSizeStack[stackIndex]) { 49 | let content: TokenStream; 50 | let types = typeArrStack[stackIndex]; 51 | const tokenArr = tokenArrStack[stackIndex]; 52 | const token = tokenArr[i]; 53 | 54 | // Determine content and append type to types if necessary 55 | if (typeof token === 'string') { 56 | types = stackIndex > 0 ? types : ['plain']; 57 | content = token; 58 | } else { 59 | types = appendTypes(types, token.type); 60 | 61 | if (token.alias) { 62 | types = appendTypes(types, token.alias); 63 | } 64 | 65 | content = token.content; 66 | } 67 | 68 | // If token.content is an array, increase the stack depth and repeat this while-loop 69 | if (typeof content !== 'string') { 70 | stackIndex++; 71 | typeArrStack.push(types); 72 | tokenArrStack.push(content as PrismToken[]); 73 | tokenArrIndexStack.push(0); 74 | tokenArrSizeStack.push(content.length); 75 | continue; 76 | } 77 | 78 | // Split by newlines 79 | const splitByNewlines = content.split(newlineRe); 80 | const newlineCount = splitByNewlines.length; 81 | currentLine.push({ 82 | types, 83 | content: splitByNewlines[0] 84 | }); 85 | 86 | // Create a new line for each string on a new line 87 | for (let i = 1; i < newlineCount; i++) { 88 | normalizeEmptyLines(currentLine); 89 | acc.push((currentLine = [])); 90 | currentLine.push({ 91 | types, 92 | content: splitByNewlines[i] 93 | }); 94 | } 95 | } 96 | 97 | // Decreate the stack depth 98 | stackIndex--; 99 | typeArrStack.pop(); 100 | tokenArrStack.pop(); 101 | tokenArrIndexStack.pop(); 102 | tokenArrSizeStack.pop(); 103 | } 104 | 105 | normalizeEmptyLines(currentLine); 106 | return acc; 107 | } 108 | 109 | export function tokenize(code: string, grammar: Grammar, language: string) { 110 | if (!grammar) { 111 | return normalizeTokens([code]); 112 | } 113 | 114 | const prismConfig: EnvConfig = { 115 | code, 116 | grammar, 117 | language, 118 | tokens: [] 119 | }; 120 | 121 | Prism.hooks.run('before-tokenize', prismConfig); 122 | prismConfig.tokens = Prism.tokenize(code, grammar); 123 | Prism.hooks.run('after-tokenize', prismConfig); 124 | return normalizeTokens(prismConfig.tokens); 125 | } 126 | 127 | const entities = [ 128 | [//g, '>'], 130 | [/{/g, '{'], 131 | [/}/g, '}'] 132 | ]; 133 | 134 | export function escape(s: string) { 135 | let newStr = s; 136 | for (let i = 0; i < entities.length; i += 1) { 137 | newStr = newStr.replace(entities[i][0], entities[i][1] as string); 138 | } 139 | return newStr; 140 | } 141 | -------------------------------------------------------------------------------- /src/docs/types.ts: -------------------------------------------------------------------------------- 1 | import type { Token as PrismToken, Grammar } from 'prismjs'; 2 | 3 | export type Language = string; 4 | export type PrismGrammar = Grammar; 5 | 6 | export type Themes = 'linear' | 'raycast' | 'vercel' | 'framer'; 7 | 8 | export type Token = { 9 | types: string[]; 10 | content: string; 11 | empty?: boolean; 12 | }; 13 | 14 | export type EnvConfig = { 15 | code: string; 16 | grammar: PrismGrammar; 17 | language: Language; 18 | tokens: (string | PrismToken)[]; 19 | }; 20 | -------------------------------------------------------------------------------- /src/docs/utils.ts: -------------------------------------------------------------------------------- 1 | import { cubicOut } from 'svelte/easing'; 2 | import type { TransitionConfig } from 'svelte/transition'; 3 | 4 | export type FlyAndScaleParams = { 5 | y?: number; 6 | start?: number; 7 | duration?: number; 8 | delay?: number; 9 | }; 10 | 11 | export const flyAndScale = ( 12 | node: Element, 13 | params: FlyAndScaleParams = { y: -8, start: 0.95, duration: 200, delay: 0 } 14 | ): TransitionConfig => { 15 | const style = getComputedStyle(node); 16 | const transform = style.transform === 'none' ? '' : style.transform; 17 | 18 | const scaleConversion = (valueA: number, scaleA: [number, number], scaleB: [number, number]) => { 19 | const [minA, maxA] = scaleA; 20 | const [minB, maxB] = scaleB; 21 | 22 | const percentage = (valueA - minA) / (maxA - minA); 23 | const valueB = percentage * (maxB - minB) + minB; 24 | 25 | return valueB; 26 | }; 27 | 28 | const styleToString = (style: Record): string => { 29 | return Object.keys(style).reduce((str, key) => { 30 | if (style[key] === undefined) return str; 31 | return str + `${key}:${style[key]};`; 32 | }, ''); 33 | }; 34 | 35 | return { 36 | duration: params.duration ?? 200, 37 | delay: params.delay ?? 0, 38 | css: (t) => { 39 | const y = scaleConversion(t, [0, 1], [params.y ?? -8, 0]); 40 | const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); 41 | 42 | return styleToString({ 43 | transform: `${transform} translate3d(0, ${y}px, 0) scale(${scale})`, 44 | opacity: t 45 | }); 46 | }, 47 | easing: cubicOut 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/lib/cmdk/command.ts: -------------------------------------------------------------------------------- 1 | import { getContext, setContext, tick } from 'svelte'; 2 | import { commandScore } from '$lib/internal/command-score.js'; 3 | import type { CommandProps, Context, Group, State, StateStore } from './types.js'; 4 | import { get, writable } from 'svelte/store'; 5 | import { 6 | omit, 7 | generateId, 8 | toWritableStores, 9 | isUndefined, 10 | kbd, 11 | removeUndefined, 12 | effect 13 | } from '$lib/internal/index.js'; 14 | 15 | const NAME = 'Command'; 16 | const STATE_NAME = 'CommandState'; 17 | const GROUP_NAME = 'CommandGroup'; 18 | 19 | export const LIST_SELECTOR = `[data-cmdk-list-sizer]`; 20 | export const GROUP_SELECTOR = `[data-cmdk-group]`; 21 | export const GROUP_ITEMS_SELECTOR = `[data-cmdk-group-items]`; 22 | export const GROUP_HEADING_SELECTOR = `[data-cmdk-group-heading]`; 23 | export const ITEM_SELECTOR = `[data-cmdk-item]`; 24 | export const VALID_ITEM_SELECTOR = `${ITEM_SELECTOR}:not([aria-disabled="true"])`; 25 | export const VALUE_ATTR = `data-value`; 26 | 27 | export const defaultFilter: (value: string, search: string) => number = (value, search) => 28 | commandScore(value, search); 29 | 30 | export function getCtx() { 31 | return getContext(NAME); 32 | } 33 | 34 | export function getState() { 35 | return getContext(STATE_NAME); 36 | } 37 | 38 | export function createGroup(alwaysRender: boolean | undefined) { 39 | const id = generateId(); 40 | 41 | setContext(GROUP_NAME, { 42 | id, 43 | alwaysRender: isUndefined(alwaysRender) ? false : alwaysRender 44 | }); 45 | return { id }; 46 | } 47 | 48 | export function getGroup() { 49 | const context = getContext(GROUP_NAME); 50 | if (!context) return undefined; 51 | return context; 52 | } 53 | 54 | export function createState(initialValues?: Partial) { 55 | const defaultState: State = { 56 | search: '', 57 | value: '', 58 | filtered: { 59 | count: 0, 60 | items: new Map(), 61 | groups: new Set() 62 | } 63 | }; 64 | const state = writable( 65 | initialValues ? { ...defaultState, ...removeUndefined(initialValues) } : defaultState 66 | ); 67 | return state; 68 | } 69 | 70 | const defaults = { 71 | label: 'Command menu', 72 | shouldFilter: true, 73 | loop: false, 74 | onValueChange: undefined, 75 | value: undefined, 76 | filter: defaultFilter, 77 | ids: { 78 | root: generateId(), 79 | list: generateId(), 80 | label: generateId(), 81 | input: generateId() 82 | } 83 | } satisfies CommandProps; 84 | 85 | export function createCommand(props: CommandProps) { 86 | const ids = { 87 | root: generateId(), 88 | list: generateId(), 89 | label: generateId(), 90 | input: generateId(), 91 | ...props.ids 92 | }; 93 | 94 | const withDefaults = { 95 | ...defaults, 96 | ...removeUndefined(props) 97 | } satisfies CommandProps; 98 | 99 | const state = 100 | props.state ?? 101 | createState({ 102 | value: withDefaults.value 103 | }); 104 | 105 | const allItems = writable>(new Set()); // [...itemIds] 106 | const allGroups = writable>>(new Map()); // groupId → [...itemIds] 107 | const allIds = writable>(new Map()); // id → value 108 | const commandEl = writable(null); 109 | 110 | const options = toWritableStores(omit(withDefaults, 'value', 'ids')); 111 | 112 | let $allItems = get(allItems); 113 | let $allGroups = get(allGroups); 114 | let $allIds = get(allIds); 115 | 116 | let shouldFilter = get(options.shouldFilter); 117 | let loop = get(options.loop); 118 | let label = get(options.label); 119 | let filter = get(options.filter); 120 | 121 | effect(options.shouldFilter, ($shouldFilter) => { 122 | shouldFilter = $shouldFilter; 123 | }); 124 | 125 | effect(options.loop, ($loop) => { 126 | loop = $loop; 127 | }); 128 | effect(options.filter, ($filter) => { 129 | filter = $filter; 130 | }); 131 | effect(options.label, ($label) => { 132 | label = $label; 133 | }); 134 | 135 | effect(allItems, (v) => { 136 | $allItems = v; 137 | }); 138 | effect(allGroups, (v) => { 139 | $allGroups = v; 140 | }); 141 | effect(allIds, (v) => { 142 | $allIds = v; 143 | }); 144 | 145 | const context: Context = { 146 | value: (id, value) => { 147 | if (value !== $allIds.get(id)) { 148 | allIds.update(($allIds) => { 149 | $allIds.set(id, value); 150 | return $allIds; 151 | }); 152 | state.update(($state) => { 153 | $state.filtered.items.set(id, score(value, $state.search)); 154 | return $state; 155 | }); 156 | } 157 | }, 158 | // Track item lifecycle (add/remove) 159 | item: (id, groupId) => { 160 | allItems.update(($allItems) => $allItems.add(id)); 161 | 162 | // Track this item within the group 163 | if (groupId) { 164 | allGroups.update(($allGroups) => { 165 | if (!$allGroups.has(groupId)) { 166 | $allGroups.set(groupId, new Set([id])); 167 | } else { 168 | $allGroups.get(groupId)?.add(id); 169 | } 170 | return $allGroups; 171 | }); 172 | } 173 | state.update(($state) => { 174 | const filteredState = filterItems($state, shouldFilter); 175 | 176 | if (!filteredState.value) { 177 | const value = selectFirstItem(); 178 | filteredState.value = value ?? ''; 179 | } 180 | return filteredState; 181 | }); 182 | 183 | return () => { 184 | allIds.update(($allIds) => { 185 | $allIds.delete(id); 186 | return $allIds; 187 | }); 188 | allItems.update(($allItems) => { 189 | $allItems.delete(id); 190 | return $allItems; 191 | }); 192 | state.update(($state) => { 193 | $state.filtered.items.delete(id); 194 | const selectedItem = getSelectedItem(); 195 | 196 | const filteredState = filterItems($state); 197 | 198 | if (selectedItem?.getAttribute('id') === id) { 199 | filteredState.value = selectFirstItem() ?? ''; 200 | } 201 | 202 | return $state; 203 | }); 204 | }; 205 | }, 206 | group: (id) => { 207 | allGroups.update(($allGroups) => { 208 | if (!$allGroups.has(id)) { 209 | $allGroups.set(id, new Set()); 210 | } 211 | return $allGroups; 212 | }); 213 | return () => { 214 | allIds.update(($allIds) => { 215 | $allIds.delete(id); 216 | return $allIds; 217 | }); 218 | allGroups.update(($allGroups) => { 219 | $allGroups.delete(id); 220 | return $allGroups; 221 | }); 222 | }; 223 | }, 224 | filter: () => { 225 | return shouldFilter; 226 | }, 227 | label: label || props['aria-label'] || '', 228 | commandEl, 229 | ids, 230 | updateState 231 | }; 232 | 233 | function updateState(key: K, value: State[K], preventScroll?: boolean) { 234 | state.update((curr) => { 235 | if (Object.is(curr[key], value)) return curr; 236 | curr[key] = value; 237 | 238 | if (key === 'search') { 239 | const filteredState = filterItems(curr, shouldFilter); 240 | curr = filteredState; 241 | const sortedState = sort(curr, shouldFilter); 242 | curr = sortedState; 243 | tick().then(() => 244 | state.update((curr) => { 245 | curr.value = selectFirstItem() ?? ''; 246 | props.onValueChange?.(curr.value); 247 | return curr; 248 | }) 249 | ); 250 | } else if (key === 'value') { 251 | props.onValueChange?.(curr.value); 252 | if (!preventScroll) { 253 | tick().then(() => scrollSelectedIntoView()); 254 | } 255 | } 256 | return curr; 257 | }); 258 | } 259 | 260 | function filterItems(state: State, shouldFilterVal?: boolean): State { 261 | const $shouldFilter = shouldFilterVal ?? shouldFilter; 262 | if (!state.search || !$shouldFilter) { 263 | state.filtered.count = $allItems.size; 264 | return state; 265 | } 266 | 267 | state.filtered.groups = new Set(); 268 | let itemCount = 0; 269 | 270 | // check which items should be included 271 | for (const id of $allItems) { 272 | const value = $allIds.get(id); 273 | const rank = score(value, state.search); 274 | state.filtered.items.set(id, rank); 275 | if (rank > 0) { 276 | itemCount++; 277 | } 278 | } 279 | 280 | // Check which groups have at least 1 item shown 281 | for (const [groupId, group] of $allGroups) { 282 | for (const itemId of group) { 283 | const rank = state.filtered.items.get(itemId); 284 | if (rank && rank > 0) { 285 | state.filtered.groups.add(groupId); 286 | } 287 | } 288 | } 289 | 290 | state.filtered.count = itemCount; 291 | return state; 292 | } 293 | 294 | function sort(state: State, shouldFilterVal?: boolean) { 295 | const $shouldFilter = shouldFilterVal ?? shouldFilter; 296 | if (!state.search || !$shouldFilter) { 297 | return state; 298 | } 299 | 300 | const scores = state.filtered.items; 301 | 302 | // sort groups 303 | const groups: [string, number][] = []; 304 | 305 | for (const value of state.filtered.groups) { 306 | const items = $allGroups.get(value); 307 | if (!items) continue; 308 | // get max score of the group's items 309 | let max = 0; 310 | for (const item of items) { 311 | const score = scores.get(item); 312 | if (isUndefined(score)) continue; 313 | max = Math.max(score, max); 314 | } 315 | groups.push([value, max]); 316 | } 317 | 318 | // Sort items within groups to bottom 319 | // sort items outside of groups 320 | // sort groups to bottom (pushed all non-grouped items to the top) 321 | const rootEl = document.getElementById(ids.root); 322 | if (!rootEl) return state; 323 | const list = rootEl.querySelector(LIST_SELECTOR); 324 | 325 | const validItems = getValidItems(rootEl).sort((a, b) => { 326 | const valueA = a.getAttribute(VALUE_ATTR) ?? ''; 327 | const valueB = b.getAttribute(VALUE_ATTR) ?? ''; 328 | return (scores.get(valueA) ?? 0) - (scores.get(valueB) ?? 0); 329 | }); 330 | 331 | for (const item of validItems) { 332 | const group = item.closest(GROUP_ITEMS_SELECTOR); 333 | const closest = item.closest(`${GROUP_ITEMS_SELECTOR} > *`); 334 | if (group) { 335 | if (item.parentElement === group) { 336 | group.appendChild(item); 337 | } else { 338 | if (!closest) continue; 339 | group.appendChild(closest); 340 | } 341 | } else { 342 | if (item.parentElement === list) { 343 | list?.appendChild(item); 344 | } else { 345 | if (!closest) continue; 346 | list?.appendChild(closest); 347 | } 348 | } 349 | } 350 | 351 | groups.sort((a, b) => b[1] - a[1]); 352 | 353 | for (const group of groups) { 354 | const el = rootEl.querySelector(`${GROUP_SELECTOR}[${VALUE_ATTR}="${group[0]}"]`); 355 | el?.parentElement?.appendChild(el); 356 | } 357 | 358 | return state; 359 | } 360 | 361 | function selectFirstItem() { 362 | const item = getValidItems().find((item) => !item.ariaDisabled); 363 | if (!item) return; 364 | const value = item.getAttribute(VALUE_ATTR); 365 | if (!value) return; 366 | return value; 367 | } 368 | 369 | function score(value: string | undefined, search: string) { 370 | const lowerCaseAndTrimmedValue = value?.toLowerCase().trim(); 371 | const filterFn = filter; 372 | if (!filterFn) { 373 | return lowerCaseAndTrimmedValue ? defaultFilter(lowerCaseAndTrimmedValue, search) : 0; 374 | } 375 | return lowerCaseAndTrimmedValue ? filterFn(lowerCaseAndTrimmedValue, search) : 0; 376 | } 377 | 378 | function scrollSelectedIntoView() { 379 | const item = getSelectedItem(); 380 | if (!item) { 381 | return; 382 | } 383 | if (item.parentElement?.firstChild === item) { 384 | tick().then(() => 385 | item.closest(GROUP_SELECTOR)?.querySelector(GROUP_HEADING_SELECTOR)?.scrollIntoView({ 386 | block: 'nearest' 387 | }) 388 | ); 389 | } 390 | tick().then(() => item.scrollIntoView({ block: 'nearest' })); 391 | } 392 | 393 | function getValidItems(rootElement?: HTMLElement) { 394 | const rootEl = rootElement ?? document.getElementById(ids.root); 395 | if (!rootEl) return []; 396 | return Array.from(rootEl.querySelectorAll(VALID_ITEM_SELECTOR)).filter( 397 | (el): el is HTMLElement => (el ? true : false) 398 | ); 399 | } 400 | 401 | function getSelectedItem(rootElement?: HTMLElement) { 402 | const rootEl = rootElement ?? document.getElementById(ids.root); 403 | if (!rootEl) return; 404 | const selectedEl = rootEl.querySelector(`${VALID_ITEM_SELECTOR}[aria-selected="true"]`); 405 | if (!selectedEl) return; 406 | return selectedEl; 407 | } 408 | 409 | function updateSelectedToIndex(index: number) { 410 | const rootEl = document.getElementById(ids.root); 411 | if (!rootEl) return; 412 | const items = getValidItems(rootEl); 413 | const item = items[index]; 414 | if (!item) return; 415 | updateState('value', item.getAttribute(VALUE_ATTR) ?? ''); 416 | } 417 | 418 | function updateSelectedByChange(change: 1 | -1) { 419 | const selected = getSelectedItem(); 420 | const items = getValidItems(); 421 | const index = items.findIndex((item) => item === selected); 422 | 423 | // get item at this index 424 | let newSelected = items[index + change]; 425 | 426 | if (loop) { 427 | if (index + change < 0) { 428 | newSelected = items[items.length - 1]; 429 | } else if (index + change === items.length) { 430 | newSelected = items[0]; 431 | } else { 432 | newSelected = items[index + change]; 433 | } 434 | } 435 | 436 | if (newSelected) { 437 | updateState('value', newSelected.getAttribute(VALUE_ATTR) ?? ''); 438 | } 439 | } 440 | 441 | function updateSelectedToGroup(change: 1 | -1) { 442 | const selected = getSelectedItem(); 443 | let group = selected?.closest(GROUP_SELECTOR); 444 | let item: HTMLElement | undefined | null = undefined; 445 | 446 | while (group && !item) { 447 | group = 448 | change > 0 449 | ? findNextSibling(group, GROUP_SELECTOR) 450 | : findPreviousSibling(group, GROUP_SELECTOR); 451 | item = group?.querySelector(VALID_ITEM_SELECTOR); 452 | } 453 | 454 | if (item) { 455 | updateState('value', item.getAttribute(VALUE_ATTR) ?? ''); 456 | } else { 457 | updateSelectedByChange(change); 458 | } 459 | } 460 | 461 | function last() { 462 | return updateSelectedToIndex(getValidItems().length - 1); 463 | } 464 | 465 | function next(e: KeyboardEvent) { 466 | e.preventDefault(); 467 | 468 | if (e.metaKey) { 469 | last(); 470 | } else if (e.altKey) { 471 | updateSelectedToGroup(1); 472 | } else { 473 | updateSelectedByChange(1); 474 | } 475 | } 476 | 477 | function prev(e: KeyboardEvent) { 478 | e.preventDefault(); 479 | 480 | if (e.metaKey) { 481 | updateSelectedToIndex(0); 482 | } else if (e.altKey) { 483 | updateSelectedToGroup(-1); 484 | } else { 485 | updateSelectedByChange(-1); 486 | } 487 | } 488 | 489 | function handleRootKeydown(e: KeyboardEvent) { 490 | switch (e.key) { 491 | case kbd.ARROW_DOWN: 492 | next(e); 493 | break; 494 | case kbd.ARROW_UP: 495 | prev(e); 496 | break; 497 | case kbd.HOME: 498 | // first item 499 | e.preventDefault(); 500 | updateSelectedToIndex(0); 501 | break; 502 | case kbd.END: 503 | // last item 504 | e.preventDefault(); 505 | last(); 506 | break; 507 | case kbd.ENTER: { 508 | e.preventDefault(); 509 | const item = getSelectedItem() as HTMLElement; 510 | if (item) { 511 | item?.click(); 512 | } 513 | } 514 | } 515 | } 516 | 517 | setContext(NAME, context); 518 | 519 | const stateStore = { 520 | subscribe: state.subscribe, 521 | update: state.update, 522 | set: state.set, 523 | updateState 524 | }; 525 | 526 | setContext(STATE_NAME, stateStore); 527 | 528 | return { 529 | state: stateStore, 530 | handleRootKeydown, 531 | commandEl, 532 | ids 533 | }; 534 | } 535 | 536 | function findNextSibling(el: Element, selector: string) { 537 | let sibling = el.nextElementSibling; 538 | 539 | while (sibling) { 540 | if (sibling.matches(selector)) return sibling; 541 | sibling = sibling.nextElementSibling; 542 | } 543 | } 544 | 545 | function findPreviousSibling(el: Element, selector: string) { 546 | let sibling = el.previousElementSibling; 547 | 548 | while (sibling) { 549 | if (sibling.matches(selector)) return sibling; 550 | sibling = sibling.previousElementSibling; 551 | } 552 | } 553 | -------------------------------------------------------------------------------- /src/lib/cmdk/components/Command.svelte: -------------------------------------------------------------------------------- 1 | 94 | 95 | {#if asChild} 96 | 97 | {:else} 98 |
99 | 100 | 103 | 104 |
105 | {/if} 106 | -------------------------------------------------------------------------------- /src/lib/cmdk/components/CommandDialog.svelte: -------------------------------------------------------------------------------- 1 | 62 | 63 | 64 | {#if portal === null} 65 | 66 | 67 | 68 | 69 | 70 | 71 | {:else} 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | {/if} 81 | 82 | -------------------------------------------------------------------------------- /src/lib/cmdk/components/CommandEmpty.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 | {#if !isFirstRender && render} 27 | {#if asChild} 28 | 29 | {:else} 30 |
31 | 32 |
33 | {/if} 34 | {/if} 35 | -------------------------------------------------------------------------------- /src/lib/cmdk/components/CommandGroup.svelte: -------------------------------------------------------------------------------- 1 | 78 | 79 | {#if asChild} 80 | 81 | {:else} 82 |
83 | {#if heading} 84 |
85 | {heading} 86 |
87 | {/if} 88 |
89 | 90 |
91 |
92 | {/if} 93 | -------------------------------------------------------------------------------- /src/lib/cmdk/components/CommandInput.svelte: -------------------------------------------------------------------------------- 1 | 69 | 70 | {#if asChild} 71 | 72 | {:else} 73 | 85 | {/if} 86 | -------------------------------------------------------------------------------- /src/lib/cmdk/components/CommandItem.svelte: -------------------------------------------------------------------------------- 1 | 90 | 91 | {#if $render || isFirstRender} 92 | {#if asChild} 93 | 94 | {:else} 95 |
96 | 97 |
98 | {/if} 99 | {/if} 100 | -------------------------------------------------------------------------------- /src/lib/cmdk/components/CommandList.svelte: -------------------------------------------------------------------------------- 1 | 58 | 59 | {#if asChild} 60 | {#key $state.search === ''} 61 | 62 | {/key} 63 | {:else} 64 |
65 |
66 | {#key $state.search === ''} 67 | 68 | {/key} 69 |
70 |
71 | {/if} 72 | -------------------------------------------------------------------------------- /src/lib/cmdk/components/CommandLoading.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | {#if asChild} 19 | 20 | {:else} 21 |
22 |
23 | 24 |
25 |
26 | {/if} 27 | -------------------------------------------------------------------------------- /src/lib/cmdk/components/CommandSeparator.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | {#if $render || alwaysRender} 21 | {#if asChild} 22 | 23 | {:else} 24 |
25 | {/if} 26 | {/if} 27 | -------------------------------------------------------------------------------- /src/lib/cmdk/index.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | LoadingProps, 3 | CommandProps, 4 | EmptyProps, 5 | ItemProps, 6 | GroupProps, 7 | ListProps, 8 | InputProps, 9 | SeparatorProps, 10 | DialogProps 11 | } from './types.js'; 12 | 13 | import Root from './components/Command.svelte'; 14 | import Dialog from './components/CommandDialog.svelte'; 15 | import Empty from './components/CommandEmpty.svelte'; 16 | import Group from './components/CommandGroup.svelte'; 17 | import Input from './components/CommandInput.svelte'; 18 | import Item from './components/CommandItem.svelte'; 19 | import List from './components/CommandList.svelte'; 20 | import Loading from './components/CommandLoading.svelte'; 21 | import Separator from './components/CommandSeparator.svelte'; 22 | 23 | export { 24 | // Components 25 | Root, 26 | Dialog, 27 | Empty, 28 | Group, 29 | Input, 30 | Item, 31 | List, 32 | Loading, 33 | Separator, 34 | // 35 | Root as CommandRoot, 36 | Dialog as CommandDialog, 37 | Empty as CommandEmpty, 38 | Group as CommandGroup, 39 | Input as CommandInput, 40 | Item as CommandItem, 41 | List as CommandList, 42 | Loading as CommandLoading, 43 | Separator as CommandSeparator 44 | }; 45 | 46 | export type { 47 | LoadingProps, 48 | DialogProps, 49 | CommandProps, 50 | EmptyProps, 51 | ItemProps, 52 | GroupProps, 53 | ListProps, 54 | InputProps, 55 | SeparatorProps 56 | }; 57 | -------------------------------------------------------------------------------- /src/lib/cmdk/types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | import type { Expand, HTMLDivAttributes, Transition, PrefixKeys } from '$lib/internal/index.js'; 3 | import type { Dialog as DialogPrimitive } from 'bits-ui'; 4 | import type { HTMLInputAttributes } from 'svelte/elements'; 5 | import type { Writable } from 'svelte/store'; 6 | 7 | // 8 | // PROPS 9 | // 10 | 11 | export type LoadingProps = { 12 | /** Estimated loading progress */ 13 | progress?: number; 14 | 15 | /** 16 | * Whether to delegate rendering to a custom element. 17 | * 18 | * The contents within the `Loading` component should be marked 19 | * as `aria-hidden` to prevent screen readers from reading the 20 | * contents while loading. 21 | */ 22 | asChild?: boolean; 23 | } & HTMLDivAttributes; 24 | 25 | export type EmptyProps = { 26 | /** 27 | * Whether to delegate rendering to a custom element. 28 | * 29 | * Only receives `attrs`, no `action`. 30 | */ 31 | asChild?: boolean; 32 | } & HTMLDivAttributes; 33 | 34 | export type SeparatorProps = { 35 | /** 36 | * Whether this separator is always rendered, regardless 37 | * of the filter. 38 | */ 39 | alwaysRender?: boolean; 40 | 41 | /** 42 | * Whether to delegate rendering to a custom element. 43 | */ 44 | asChild?: boolean; 45 | } & HTMLDivAttributes; 46 | 47 | type BaseCommandProps = { 48 | /** 49 | * Controlled state store for the command menu. 50 | * Initialize state using the `createState` function. 51 | */ 52 | state?: Writable; 53 | 54 | /** 55 | * An accessible label for the command menu. 56 | * Not visible & only used for screen readers. 57 | */ 58 | label?: string; 59 | 60 | /** 61 | * Optionally set to `false` to turn off the automatic filtering 62 | * and sorting. If `false`, you must conditionally render valid 63 | * items yourself. 64 | */ 65 | shouldFilter?: boolean; 66 | 67 | /** 68 | * A custom filter function for whether each command item should 69 | * match the query. It should return a number between `0` and `1`, 70 | * with `1` being a perfect match, and `0` being no match, resulting 71 | * in the item being hidden entirely. 72 | * 73 | * By default, it will use the `command-score` package to score. 74 | */ 75 | filter?: (value: string, search: string) => number; 76 | 77 | /** 78 | * Optionally provide or bind to the selected command menu item. 79 | */ 80 | value?: string; 81 | 82 | /** 83 | * A function that is called when the selected command menu item 84 | * changes. It receives the new value as an argument. 85 | */ 86 | onValueChange?: (value: string) => void; 87 | 88 | /** 89 | * Optionally set to `true` to enable looping through the items 90 | * when the user reaches the end of the list using the keyboard. 91 | */ 92 | loop?: boolean; 93 | }; 94 | 95 | export type CommandProps = Expand< 96 | BaseCommandProps & { 97 | /** 98 | * Optionally provide custom ids for the command menu 99 | * elements. These ids should be unique and are only 100 | * necessary in very specific cases. Use with caution. 101 | */ 102 | ids?: Partial; 103 | } 104 | > & 105 | HTMLDivAttributes & { 106 | onKeydown?: (e: KeyboardEvent) => void; 107 | asChild?: boolean; 108 | }; 109 | 110 | export type ListProps = { 111 | /** 112 | * The list element 113 | */ 114 | el?: HTMLElement; 115 | 116 | /** 117 | * Whether to delegate rendering to a custom element. 118 | * 119 | * Provides 2 slot props: `container` & `list`. 120 | * Container only has an `attrs` property, while `list` has 121 | * `attrs` & `action` to be applied to the respective elements. 122 | * 123 | * The `list` wraps the `sizer`, and the `sizer` wraps the `items`, and 124 | * is responsible for measuring the height of the items and setting the 125 | * CSS variable to the height of the items. 126 | */ 127 | asChild?: boolean; 128 | } & HTMLDivAttributes; 129 | 130 | export type InputProps = { 131 | /** 132 | * The input element 133 | */ 134 | el?: HTMLInputElement; 135 | 136 | /** 137 | * Whether to delegate rendering to a custom element. 138 | */ 139 | asChild?: boolean; 140 | } & HTMLInputAttributes; 141 | 142 | export type GroupProps = { 143 | /** 144 | * Optional heading to render for the group 145 | */ 146 | heading?: string; 147 | 148 | /** 149 | * If heading isn't provided, you must provide a unique 150 | * value for the group. 151 | */ 152 | value?: string; 153 | 154 | /** 155 | * Whether or not this group is always rendered, 156 | * regardless of filtering. 157 | */ 158 | alwaysRender?: boolean; 159 | 160 | /** 161 | * Whether to delegate rendering to custom elements. 162 | * 163 | * Provides 3 slot props: `container`, `heading`, and `group`. 164 | * Container has `attrs` & `action`, while `heading` & `group` 165 | * only have `attrs` to be applied to the respective elements. 166 | */ 167 | asChild?: boolean; 168 | } & HTMLDivAttributes; 169 | 170 | export type ItemProps = { 171 | /** 172 | * Whether this item is disabled. 173 | */ 174 | disabled?: boolean; 175 | 176 | /** 177 | * A function called when this item is selected, either 178 | * via click or keyboard selection. 179 | */ 180 | onSelect?: (value: string) => void; 181 | 182 | /** 183 | * A unique value for this item. 184 | * If not provided, it will be inferred from the rendered 185 | * `textContent`. If your `textContent` is dynamic, you must 186 | * provide a stable unique `value`. 187 | */ 188 | value?: string; 189 | 190 | /** 191 | * Whether or not this item is always rendered, 192 | * regardless of filtering. 193 | */ 194 | alwaysRender?: boolean; 195 | 196 | /** 197 | * Whether to delegate rendering to a custom element. 198 | * Will pass the `attrs` & `action` to be applied to the custom element. 199 | */ 200 | asChild?: boolean; 201 | 202 | /** 203 | * Optionally override the default `id` generated for this item. 204 | * NOTE: This must be unique across all items and is only necessary 205 | * in very specific cases. 206 | */ 207 | id?: string; 208 | } & HTMLDivAttributes; 209 | 210 | type TransitionProps = 211 | | 'transition' 212 | | 'transitionConfig' 213 | | 'inTransition' 214 | | 'inTransitionConfig' 215 | | 'outTransition' 216 | | 'outTransitionConfig'; 217 | 218 | export type OverlayProps< 219 | T extends Transition = Transition, 220 | In extends Transition = Transition, 221 | Out extends Transition = Transition 222 | > = PrefixKeys, TransitionProps>, 'overlay'> & { 223 | overlayClasses?: string; 224 | }; 225 | 226 | export type ContentProps< 227 | T extends Transition = Transition, 228 | In extends Transition = Transition, 229 | Out extends Transition = Transition 230 | > = PrefixKeys, TransitionProps>, 'content'> & { 231 | contentClasses?: string; 232 | }; 233 | 234 | export type DialogProps< 235 | ContentT extends Transition = Transition, 236 | ContentIn extends Transition = Transition, 237 | ContentOut extends Transition = Transition, 238 | OverlayT extends Transition = Transition, 239 | OverlayIn extends Transition = Transition, 240 | OverlayOut extends Transition = Transition 241 | > = CommandProps & 242 | DialogPrimitive.Props & 243 | OverlayProps & 244 | ContentProps; 245 | 246 | // 247 | // Events 248 | // 249 | 250 | export type InputEvents = { 251 | keydown: KeyboardEvent; 252 | blur: FocusEvent; 253 | input: Event; 254 | focus: FocusEvent; 255 | change: Event; 256 | }; 257 | 258 | // 259 | // Internal 260 | // 261 | export type CommandOptionStores = { 262 | [K in keyof Omit, 'value'>]: Writable; 263 | }; 264 | 265 | export type State = { 266 | /** The value of the search query */ 267 | search: string; 268 | /** The value of the selected command menu item */ 269 | value: string; 270 | /** The filtered items */ 271 | filtered: { 272 | /** The count of all visible items. */ 273 | count: number; 274 | /** Map from visible item id to its search store. */ 275 | items: Map; 276 | /** Set of groups with at least one visible item. */ 277 | groups: Set; 278 | }; 279 | }; 280 | 281 | export type CommandIds = Record<'root' | 'label' | 'input' | 'list', string>; 282 | 283 | export type Context = { 284 | value: (id: string, value: string) => void; 285 | item: (id: string, groupId: string | undefined) => () => void; 286 | group: (id: string) => () => void; 287 | filter: () => boolean; 288 | label: string; 289 | commandEl: Writable; 290 | ids: CommandIds; 291 | updateState: UpdateState; 292 | }; 293 | 294 | type UpdateState = ( 295 | key: K, 296 | value: State[K], 297 | preventScroll?: boolean 298 | ) => void; 299 | 300 | export type ConextStore = Writable; 301 | 302 | export type StateStore = Writable & { 303 | updateState: UpdateState; 304 | }; 305 | 306 | export type Group = { 307 | id: string; 308 | alwaysRender: boolean; 309 | }; 310 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | import { createState, defaultFilter } from './cmdk/command.js'; 2 | import { 3 | CommandRoot, 4 | CommandDialog, 5 | CommandEmpty, 6 | CommandList, 7 | CommandItem, 8 | CommandGroup, 9 | CommandInput, 10 | CommandLoading, 11 | CommandSeparator 12 | } from './cmdk/index.js'; 13 | 14 | export * as Command from './cmdk/index.js'; 15 | export type * from './cmdk/types.js'; 16 | export { createState, defaultFilter }; 17 | 18 | export { 19 | CommandRoot, 20 | CommandDialog, 21 | CommandEmpty, 22 | CommandList, 23 | CommandItem, 24 | CommandGroup, 25 | CommandInput, 26 | CommandLoading, 27 | CommandSeparator 28 | }; 29 | -------------------------------------------------------------------------------- /src/lib/internal/command-score.ts: -------------------------------------------------------------------------------- 1 | // The scores are arranged so that a continuous match of characters will 2 | // result in a total score of 1. 3 | // 4 | // The best case, this character is a match, and either this is the start 5 | // of the string, or the previous character was also a match. 6 | const SCORE_CONTINUE_MATCH = 1, 7 | // A new match at the start of a word scores better than a new match 8 | // elsewhere as it's more likely that the user will type the starts 9 | // of fragments. 10 | // NOTE: We score word jumps between spaces slightly higher than slashes, brackets 11 | // hyphens, etc. 12 | SCORE_SPACE_WORD_JUMP = 0.9, 13 | SCORE_NON_SPACE_WORD_JUMP = 0.8, 14 | // Any other match isn't ideal, but we include it for completeness. 15 | SCORE_CHARACTER_JUMP = 0.17, 16 | // If the user transposed two letters, it should be significantly penalized. 17 | // 18 | // i.e. "ouch" is more likely than "curtain" when "uc" is typed. 19 | SCORE_TRANSPOSITION = 0.1, 20 | // The goodness of a match should decay slightly with each missing 21 | // character. 22 | // 23 | // i.e. "bad" is more likely than "bard" when "bd" is typed. 24 | // 25 | // This will not change the order of suggestions based on SCORE_* until 26 | // 100 characters are inserted between matches. 27 | PENALTY_SKIPPED = 0.999, 28 | // The goodness of an exact-case match should be higher than a 29 | // case-insensitive match by a small amount. 30 | // 31 | // i.e. "HTML" is more likely than "haml" when "HM" is typed. 32 | // 33 | // This will not change the order of suggestions based on SCORE_* until 34 | // 1000 characters are inserted between matches. 35 | PENALTY_CASE_MISMATCH = 0.9999, 36 | // If the word has more characters than the user typed, it should 37 | // be penalised slightly. 38 | // 39 | // i.e. "html" is more likely than "html5" if I type "html". 40 | // 41 | // However, it may well be the case that there's a sensible secondary 42 | // ordering (like alphabetical) that it makes sense to rely on when 43 | // there are many prefix matches, so we don't make the penalty increase 44 | // with the number of tokens. 45 | PENALTY_NOT_COMPLETE = 0.99; 46 | 47 | const IS_GAP_REGEXP = /[\\/_+.#"@[({&]/, 48 | COUNT_GAPS_REGEXP = /[\\/_+.#"@[({&]/g, 49 | IS_SPACE_REGEXP = /[\s-]/, 50 | COUNT_SPACE_REGEXP = /[\s-]/g; 51 | 52 | function commandScoreInner( 53 | string: string, 54 | abbreviation: string, 55 | lowerString: string, 56 | lowerAbbreviation: string, 57 | stringIndex: number, 58 | abbreviationIndex: number, 59 | memoizedResults: { [key: string]: number } 60 | ) { 61 | if (abbreviationIndex === abbreviation.length) { 62 | if (stringIndex === string.length) { 63 | return SCORE_CONTINUE_MATCH; 64 | } 65 | return PENALTY_NOT_COMPLETE; 66 | } 67 | 68 | const memoizeKey = `${stringIndex},${abbreviationIndex}`; 69 | if (memoizedResults[memoizeKey] !== undefined) { 70 | return memoizedResults[memoizeKey]; 71 | } 72 | 73 | const abbreviationChar = lowerAbbreviation.charAt(abbreviationIndex); 74 | let index = lowerString.indexOf(abbreviationChar, stringIndex); 75 | let highScore = 0; 76 | 77 | let score, transposedScore, wordBreaks, spaceBreaks; 78 | 79 | while (index >= 0) { 80 | score = commandScoreInner( 81 | string, 82 | abbreviation, 83 | lowerString, 84 | lowerAbbreviation, 85 | index + 1, 86 | abbreviationIndex + 1, 87 | memoizedResults 88 | ); 89 | if (score > highScore) { 90 | if (index === stringIndex) { 91 | score *= SCORE_CONTINUE_MATCH; 92 | } else if (IS_GAP_REGEXP.test(string.charAt(index - 1))) { 93 | score *= SCORE_NON_SPACE_WORD_JUMP; 94 | wordBreaks = string.slice(stringIndex, index - 1).match(COUNT_GAPS_REGEXP); 95 | if (wordBreaks && stringIndex > 0) { 96 | score *= Math.pow(PENALTY_SKIPPED, wordBreaks.length); 97 | } 98 | } else if (IS_SPACE_REGEXP.test(string.charAt(index - 1))) { 99 | score *= SCORE_SPACE_WORD_JUMP; 100 | spaceBreaks = string.slice(stringIndex, index - 1).match(COUNT_SPACE_REGEXP); 101 | if (spaceBreaks && stringIndex > 0) { 102 | score *= Math.pow(PENALTY_SKIPPED, spaceBreaks.length); 103 | } 104 | } else { 105 | score *= SCORE_CHARACTER_JUMP; 106 | if (stringIndex > 0) { 107 | score *= Math.pow(PENALTY_SKIPPED, index - stringIndex); 108 | } 109 | } 110 | 111 | if (string.charAt(index) !== abbreviation.charAt(abbreviationIndex)) { 112 | score *= PENALTY_CASE_MISMATCH; 113 | } 114 | } 115 | 116 | if ( 117 | (score < SCORE_TRANSPOSITION && 118 | lowerString.charAt(index - 1) === lowerAbbreviation.charAt(abbreviationIndex + 1)) || 119 | (lowerAbbreviation.charAt(abbreviationIndex + 1) === 120 | lowerAbbreviation.charAt(abbreviationIndex) && // allow duplicate letters. Ref #7428 121 | lowerString.charAt(index - 1) !== lowerAbbreviation.charAt(abbreviationIndex)) 122 | ) { 123 | transposedScore = commandScoreInner( 124 | string, 125 | abbreviation, 126 | lowerString, 127 | lowerAbbreviation, 128 | index + 1, 129 | abbreviationIndex + 2, 130 | memoizedResults 131 | ); 132 | 133 | if (transposedScore * SCORE_TRANSPOSITION > score) { 134 | score = transposedScore * SCORE_TRANSPOSITION; 135 | } 136 | } 137 | 138 | if (score > highScore) { 139 | highScore = score; 140 | } 141 | 142 | index = lowerString.indexOf(abbreviationChar, index + 1); 143 | } 144 | 145 | memoizedResults[memoizeKey] = highScore; 146 | return highScore; 147 | } 148 | 149 | function formatInput(string: string) { 150 | // convert all valid space characters to space so they match each other 151 | return string.toLowerCase().replace(COUNT_SPACE_REGEXP, ' '); 152 | } 153 | 154 | export function commandScore(string: string, abbreviation: string) { 155 | /* NOTE: 156 | * in the original, we used to do the lower-casing on each recursive call, but this meant that toLowerCase() 157 | * was the dominating cost in the algorithm, passing both is a little ugly, but considerably faster. 158 | */ 159 | return commandScoreInner( 160 | string, 161 | abbreviation, 162 | formatInput(string), 163 | formatInput(abbreviation), 164 | 0, 165 | 0, 166 | {} 167 | ); 168 | } 169 | -------------------------------------------------------------------------------- /src/lib/internal/helpers/callbacks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A callback function that takes an array of arguments of type `T` and returns `void`. 3 | * @template T The types of the arguments that the callback function takes. 4 | */ 5 | export type Callback = (...args: T) => void; 6 | 7 | /** 8 | * Executes an array of callback functions with the same arguments. 9 | * @template T The types of the arguments that the callback functions take. 10 | * @param n array of callback functions to execute. 11 | * @returns A new function that executes all of the original callback functions with the same arguments. 12 | */ 13 | export function executeCallbacks( 14 | ...callbacks: Array> 15 | ): (...args: T) => void { 16 | return (...args) => { 17 | for (const callback of callbacks) { 18 | if (typeof callback === 'function') { 19 | callback(...args); 20 | } 21 | } 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/internal/helpers/event.ts: -------------------------------------------------------------------------------- 1 | import type { Arrayable } from '$lib/internal/index.js'; 2 | 3 | /** 4 | * A type alias for a general event listener function. 5 | * 6 | * @template E - The type of event to listen for 7 | * @param evt - The event object 8 | * @returns The return value of the event listener function 9 | */ 10 | export type GeneralEventListener = (evt: E) => unknown; 11 | 12 | /** 13 | * Overloaded function signatures for addEventListener 14 | */ 15 | export function addEventListener( 16 | target: Window, 17 | event: E, 18 | handler: (this: Window, ev: HTMLElementEventMap[E]) => unknown, 19 | options?: boolean | AddEventListenerOptions 20 | ): VoidFunction; 21 | 22 | export function addEventListener( 23 | target: Document, 24 | event: E, 25 | handler: (this: Document, ev: HTMLElementEventMap[E]) => unknown, 26 | options?: boolean | AddEventListenerOptions 27 | ): VoidFunction; 28 | 29 | export function addEventListener( 30 | target: EventTarget, 31 | event: E, 32 | handler: GeneralEventListener, 33 | options?: boolean | AddEventListenerOptions 34 | ): VoidFunction; 35 | 36 | /** 37 | * Adds an event listener to the specified target element(s) for the given event(s), and returns a function to remove it. 38 | * @param target The target element(s) to add the event listener to. 39 | * @param event The event(s) to listen for. 40 | * @param handler The function to be called when the event is triggered. 41 | * @param options An optional object that specifies characteristics about the event listener. 42 | * @returns A function that removes the event listener from the target element(s). 43 | */ 44 | export function addEventListener( 45 | target: Window | Document | EventTarget, 46 | event: Arrayable, 47 | handler: EventListenerOrEventListenerObject, 48 | options?: boolean | AddEventListenerOptions 49 | ) { 50 | const events = Array.isArray(event) ? event : [event]; 51 | 52 | // Add the event listener to each specified event for the target element(s). 53 | events.forEach((_event) => target.addEventListener(_event, handler, options)); 54 | 55 | // Return a function that removes the event listener from the target element(s). 56 | return () => { 57 | events.forEach((_event) => target.removeEventListener(_event, handler, options)); 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /src/lib/internal/helpers/id.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid/non-secure'; 2 | 3 | export function generateId() { 4 | return nanoid(10); 5 | } 6 | -------------------------------------------------------------------------------- /src/lib/internal/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './is.js'; 2 | export * from './id.js'; 3 | export * from './kbd.js'; 4 | export * from './object.js'; 5 | export * from './store.js'; 6 | export * from './style.js'; 7 | export * from './event.js'; 8 | export * from './callbacks.js'; 9 | -------------------------------------------------------------------------------- /src/lib/internal/helpers/is.ts: -------------------------------------------------------------------------------- 1 | export const isBrowser = typeof document !== 'undefined'; 2 | 3 | export function isHTMLElement(element: unknown): element is HTMLElement { 4 | return element instanceof HTMLElement; 5 | } 6 | 7 | export function isHTMLInputElement(element: unknown): element is HTMLInputElement { 8 | return element instanceof HTMLInputElement; 9 | } 10 | 11 | export function isUndefined(value: unknown): value is undefined { 12 | return value === undefined; 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/internal/helpers/kbd.ts: -------------------------------------------------------------------------------- 1 | export const kbd = { 2 | ALT: 'Alt', 3 | ARROW_DOWN: 'ArrowDown', 4 | ARROW_LEFT: 'ArrowLeft', 5 | ARROW_RIGHT: 'ArrowRight', 6 | ARROW_UP: 'ArrowUp', 7 | BACKSPACE: 'Backspace', 8 | CAPS_LOCK: 'CapsLock', 9 | CONTROL: 'Control', 10 | DELETE: 'Delete', 11 | END: 'End', 12 | ENTER: 'Enter', 13 | ESCAPE: 'Escape', 14 | F1: 'F1', 15 | F10: 'F10', 16 | F11: 'F11', 17 | F12: 'F12', 18 | F2: 'F2', 19 | F3: 'F3', 20 | F4: 'F4', 21 | F5: 'F5', 22 | F6: 'F6', 23 | F7: 'F7', 24 | F8: 'F8', 25 | F9: 'F9', 26 | HOME: 'Home', 27 | META: 'Meta', 28 | PAGE_DOWN: 'PageDown', 29 | PAGE_UP: 'PageUp', 30 | SHIFT: 'Shift', 31 | SPACE: ' ', 32 | TAB: 'Tab', 33 | CTRL: 'Control', 34 | ASTERISK: '*' 35 | }; 36 | -------------------------------------------------------------------------------- /src/lib/internal/helpers/object.ts: -------------------------------------------------------------------------------- 1 | import type { ValueOf } from '$lib/internal/types.js'; 2 | 3 | export function omit, K extends keyof T>( 4 | obj: T, 5 | ...keys: K[] 6 | ): Omit { 7 | const result = {} as Omit; 8 | for (const key of Object.keys(obj)) { 9 | if (!keys.includes(key as unknown as K)) { 10 | result[key as keyof Omit] = obj[key] as ValueOf>; 11 | } 12 | } 13 | return result; 14 | } 15 | 16 | export function removeUndefined(obj: T): T { 17 | const result = {} as T; 18 | for (const key in obj) { 19 | const value = obj[key]; 20 | if (value !== undefined) { 21 | result[key] = value; 22 | } 23 | } 24 | return result; 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/internal/helpers/sleep.ts: -------------------------------------------------------------------------------- 1 | export function sleep(ms: number) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/internal/helpers/store.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Writable, 3 | type Stores, 4 | type StoresValues, 5 | type Readable, 6 | derived, 7 | writable 8 | } from 'svelte/store'; 9 | import { onDestroy } from 'svelte'; 10 | 11 | /** 12 | * Given an object of properties, returns an object of writable stores 13 | * with the same properties and values. 14 | */ 15 | export function toWritableStores>( 16 | properties: T 17 | ): { [K in keyof T]: Writable } { 18 | const result = {} as { [K in keyof T]: Writable }; 19 | 20 | Object.keys(properties).forEach((key) => { 21 | const propertyKey = key as keyof T; 22 | const value = properties[propertyKey]; 23 | result[propertyKey] = writable(value); 24 | }); 25 | 26 | return result; 27 | } 28 | 29 | /** 30 | * A utility function that creates an effect from a set of stores and a function. 31 | * The effect is automatically cleaned up when the component is destroyed. 32 | * 33 | * @template S - The type of the stores object 34 | * @param stores - The stores object to derive from 35 | * @param fn - The function to run when the stores change 36 | * @returns A function that can be used to unsubscribe the effect 37 | */ 38 | export function effect( 39 | stores: S, 40 | fn: (values: StoresValues) => (() => void) | void 41 | ): () => void { 42 | // Create a derived store that contains the stores object and an onUnsubscribe function 43 | const unsub = derivedWithUnsubscribe(stores, (stores, onUnsubscribe) => { 44 | return { 45 | stores, 46 | onUnsubscribe 47 | }; 48 | }).subscribe(({ stores, onUnsubscribe }) => { 49 | const returned = fn(stores); 50 | // If the function returns a cleanup function, call it when the effect is unsubscribed 51 | if (returned) { 52 | onUnsubscribe(returned); 53 | } 54 | }); 55 | 56 | // Automatically unsubscribe the effect when the component is destroyed 57 | onDestroy(unsub); 58 | return unsub; 59 | } 60 | 61 | /** 62 | * A utility function that creates a derived store that automatically 63 | * unsubscribes from its dependencies. 64 | * 65 | * @template S - The type of the stores object 66 | * @template T - The type of the derived store 67 | * @param stores - The stores object to derive from 68 | * @param fn - The function to derive the store from 69 | * @returns A derived store that automatically unsubscribes from its dependencies 70 | */ 71 | export function derivedWithUnsubscribe( 72 | stores: S, 73 | fn: (values: StoresValues, onUnsubscribe: (cb: () => void) => void) => T 74 | ): Readable { 75 | let unsubscribers: (() => void)[] = []; 76 | const onUnsubscribe = (cb: () => void) => { 77 | unsubscribers.push(cb); 78 | }; 79 | 80 | const unsubscribe = () => { 81 | // Call all of the unsubscribe functions from the previous run of the function 82 | unsubscribers.forEach((fn) => fn()); 83 | // Clear the list of unsubscribe functions 84 | unsubscribers = []; 85 | }; 86 | 87 | const derivedStore = derived(stores, ($storeValues) => { 88 | unsubscribe(); 89 | return fn($storeValues, onUnsubscribe); 90 | }); 91 | 92 | onDestroy(unsubscribe); 93 | 94 | const subscribe: typeof derivedStore.subscribe = (...args) => { 95 | const unsub = derivedStore.subscribe(...args); 96 | return () => { 97 | unsub(); 98 | unsubscribe(); 99 | }; 100 | }; 101 | 102 | return { 103 | ...derivedStore, 104 | subscribe 105 | }; 106 | } 107 | -------------------------------------------------------------------------------- /src/lib/internal/helpers/style.ts: -------------------------------------------------------------------------------- 1 | export function styleToString(style: Record): string { 2 | return Object.keys(style).reduce((str, key) => { 3 | if (style[key] === undefined) return str; 4 | return str + `${key}:${style[key]};`; 5 | }, ''); 6 | } 7 | 8 | export const srOnlyStyles = { 9 | position: 'absolute', 10 | width: '1px', 11 | height: '1px', 12 | padding: '0', 13 | margin: '-1px', 14 | overflow: 'hidden', 15 | clip: 'rect(0, 0, 0, 0)', 16 | whiteSpace: 'nowrap', 17 | borderWidth: '0' 18 | }; 19 | -------------------------------------------------------------------------------- /src/lib/internal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './command-score.js'; 2 | export * from './helpers/index.js'; 3 | export * from './types.js'; 4 | -------------------------------------------------------------------------------- /src/lib/internal/types.ts: -------------------------------------------------------------------------------- 1 | import type { HTMLAttributes } from 'svelte/elements'; 2 | import type { TransitionConfig } from 'svelte/transition'; 3 | 4 | export type Expand = T extends object 5 | ? T extends infer O 6 | ? { [K in keyof O]: O[K] } 7 | : never 8 | : T; 9 | 10 | export type ValueOf = T[keyof T]; 11 | 12 | export type HTMLDivAttributes = HTMLAttributes; 13 | 14 | export type Prettify = { 15 | [K in keyof T]: T[K]; 16 | // eslint-disable-next-line @typescript-eslint/ban-types 17 | } & {}; 18 | 19 | export type RenameProperties>> = Expand<{ 20 | [K in keyof T as K extends keyof NewNames 21 | ? NewNames[K] extends PropertyKey 22 | ? NewNames[K] 23 | : K 24 | : K]: T[K]; 25 | }>; 26 | 27 | export type PrefixKeys = Expand<{ 28 | [K in keyof T as `${Prefix}${Capitalize}`]: T[K]; 29 | }>; 30 | 31 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 32 | export type Transition = (node: Element, params?: any) => TransitionConfig; 33 | 34 | export type Arrayable = T | T[]; 35 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | LoadingProps, 3 | CommandProps, 4 | EmptyProps, 5 | ItemProps, 6 | GroupProps, 7 | ListProps, 8 | InputProps, 9 | SeparatorProps, 10 | DialogProps, 11 | State 12 | } from './cmdk/types.js'; 13 | 14 | export type { 15 | LoadingProps, 16 | CommandProps, 17 | EmptyProps, 18 | ItemProps, 19 | GroupProps, 20 | ListProps, 21 | InputProps, 22 | SeparatorProps, 23 | DialogProps, 24 | State 25 | }; 26 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | {description} - {title} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutLoad } from './$types.js'; 2 | import packageJSON from '../../package.json'; 3 | 4 | export const load: LayoutLoad = async () => { 5 | return { 6 | version: packageJSON.version 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 |
19 |
20 |
21 | 22 |

⌘K-sv

23 |

Fast, composable, unstyled command menu for Svelte.

24 |
25 |
26 | 27 | 28 |
29 |
30 |
31 | {#if theme === 'raycast'} 32 | 33 | 34 | 35 | {:else if theme === 'linear'} 36 | 37 | 38 | 39 | {:else if theme === 'vercel'} 40 | 41 | 42 | 43 | {:else if theme === 'framer'} 44 | 45 | 46 | 47 | {/if} 48 |
49 | 50 | 51 |
52 | 53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /src/routes/sink/+page.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 25 | 26 | No item found. 27 | 28 | 32 | {#each names as txt (txt)} 33 | {txt} 34 | {/each} 35 | 36 | 37 |
38 | -------------------------------------------------------------------------------- /src/styles/app.postcss: -------------------------------------------------------------------------------- 1 | .main { 2 | width: 100vw; 3 | min-height: 100vh; 4 | position: relative; 5 | display: flex; 6 | justify-content: center; 7 | padding: 120px 24px 160px 24px; 8 | 9 | &:before { 10 | background: radial-gradient(circle, rgba(2, 0, 36, 0) 0, var(--gray1) 100%); 11 | position: absolute; 12 | content: ''; 13 | z-index: 2; 14 | width: 100%; 15 | height: 100%; 16 | top: 0; 17 | } 18 | 19 | &:after { 20 | content: ''; 21 | background-image: url('/grid.svg'); 22 | position: absolute; 23 | z-index: -1; 24 | top: 0; 25 | width: 100%; 26 | height: 100%; 27 | opacity: 0.2; 28 | filter: invert(1); 29 | } 30 | 31 | @media (prefers-color-scheme: dark) { 32 | &:after { 33 | filter: unset; 34 | } 35 | } 36 | 37 | & h1 { 38 | font-size: 32px; 39 | color: var(--gray12); 40 | font-weight: 600; 41 | letter-spacing: -2px; 42 | line-height: 40px; 43 | } 44 | 45 | & p { 46 | color: var(--gray11); 47 | margin-top: 8px; 48 | font-size: 16px; 49 | } 50 | } 51 | 52 | .content { 53 | height: fit-content; 54 | position: relative; 55 | z-index: 3; 56 | width: 100%; 57 | max-width: 640px; 58 | 59 | &:after { 60 | background-image: radial-gradient(at 27% 37%, hsla(215, 98%, 61%, 1) 0px, transparent 50%), 61 | radial-gradient(at 97% 21%, hsla(256, 98%, 72%, 1) 0px, transparent 50%), 62 | radial-gradient(at 52% 99%, hsla(354, 98%, 61%, 1) 0px, transparent 50%), 63 | radial-gradient(at 10% 29%, hsla(133, 96%, 67%, 1) 0px, transparent 50%), 64 | radial-gradient(at 97% 96%, hsla(38, 60%, 74%, 1) 0px, transparent 50%), 65 | radial-gradient(at 33% 50%, hsla(222, 67%, 73%, 1) 0px, transparent 50%), 66 | radial-gradient(at 79% 53%, hsla(343, 68%, 79%, 1) 0px, transparent 50%); 67 | position: absolute; 68 | content: ''; 69 | z-index: 2; 70 | width: 100%; 71 | height: 100%; 72 | filter: blur(100px) saturate(150%); 73 | z-index: -1; 74 | top: 80px; 75 | opacity: 0.2; 76 | transform: translateZ(0); 77 | } 78 | 79 | @media (prefers-color-scheme: dark) { 80 | &:after { 81 | opacity: 0.1; 82 | } 83 | } 84 | } 85 | 86 | .meta { 87 | display: flex; 88 | align-items: center; 89 | justify-content: space-between; 90 | margin-bottom: 48px; 91 | flex-wrap: wrap; 92 | gap: 16px; 93 | } 94 | 95 | .buttons { 96 | display: flex; 97 | flex-direction: column; 98 | align-items: flex-end; 99 | gap: 12px; 100 | transform: translateY(12px); 101 | } 102 | 103 | .githubButton, 104 | .installButton, 105 | .switcher button { 106 | height: 40px; 107 | color: var(--gray12); 108 | border-radius: 9999px; 109 | font-size: 14px; 110 | transition-duration: 150ms; 111 | transition-property: background, color, transform; 112 | transition-timing-function: ease-in; 113 | will-change: transform; 114 | } 115 | 116 | .githubButton { 117 | width: 200px; 118 | padding: 0 12px; 119 | display: inline-flex; 120 | align-items: center; 121 | gap: 8px; 122 | font-weight: 500; 123 | 124 | &:hover { 125 | background: var(--grayA3); 126 | } 127 | 128 | &:active { 129 | background: var(--grayA5); 130 | transform: scale(0.97); 131 | } 132 | 133 | &:focus-visible { 134 | outline: 0; 135 | outline: 2px solid var(--gray7); 136 | } 137 | } 138 | 139 | .installButton { 140 | background: var(--grayA3); 141 | display: flex; 142 | align-items: center; 143 | gap: 16px; 144 | padding: 0px 8px 0 16px; 145 | cursor: copy; 146 | font-weight: 500; 147 | 148 | &:hover { 149 | background: var(--grayA4); 150 | 151 | & span { 152 | background: var(--grayA5); 153 | 154 | & svg { 155 | color: var(--gray12); 156 | } 157 | } 158 | } 159 | 160 | &:focus-visible { 161 | outline: 0; 162 | outline: 2px solid var(--gray7); 163 | outline-offset: 2px; 164 | } 165 | 166 | &:active { 167 | background: var(--gray5); 168 | transform: scale(0.97); 169 | } 170 | 171 | & span { 172 | width: 28px; 173 | height: 28px; 174 | display: flex; 175 | align-items: center; 176 | justify-content: center; 177 | margin-left: auto; 178 | background: var(--grayA3); 179 | border-radius: 9999px; 180 | transition: background 150ms ease; 181 | 182 | & svg { 183 | size: 16px; 184 | color: var(--gray11); 185 | transition: color 150ms ease; 186 | } 187 | } 188 | } 189 | 190 | .switcher { 191 | display: grid; 192 | grid-template-columns: repeat(4, 100px); 193 | align-items: center; 194 | justify-content: center; 195 | gap: 4px; 196 | margin-top: 48px; 197 | position: relative; 198 | 199 | & button { 200 | height: 32px; 201 | line-height: 32px; 202 | display: flex; 203 | align-items: center; 204 | margin: auto; 205 | gap: 8px; 206 | padding: 0 16px; 207 | border-radius: 9999px; 208 | color: var(--gray11); 209 | font-size: 14px; 210 | cursor: pointer; 211 | user-select: none; 212 | position: relative; 213 | text-transform: capitalize; 214 | 215 | &:hover { 216 | color: var(--gray12); 217 | } 218 | 219 | &:active { 220 | transform: scale(0.96); 221 | } 222 | 223 | &:focus-visible { 224 | outline: 0; 225 | outline: 2px solid var(--gray7); 226 | } 227 | 228 | & svg { 229 | width: 14px; 230 | height: 14px; 231 | } 232 | 233 | &[data-selected='true'] { 234 | color: var(--gray12); 235 | 236 | &:hover .activeTheme { 237 | background: var(--grayA6); 238 | } 239 | 240 | &:active { 241 | transform: scale(0.96); 242 | 243 | .activeTheme { 244 | background: var(--grayA7); 245 | } 246 | } 247 | } 248 | } 249 | 250 | .activeTheme { 251 | background: var(--grayA5); 252 | border-radius: 9999px; 253 | height: 32px; 254 | width: 100%; 255 | top: 0; 256 | position: absolute; 257 | left: 0; 258 | } 259 | 260 | .arrow { 261 | color: var(--gray11); 262 | user-select: none; 263 | position: absolute; 264 | } 265 | } 266 | 267 | .header { 268 | position: absolute; 269 | left: 0; 270 | top: -64px; 271 | gap: 8px; 272 | background: var(--gray3); 273 | padding: 4px; 274 | display: flex; 275 | align-items: center; 276 | border-radius: 9999px; 277 | 278 | & button { 279 | display: flex; 280 | align-items: center; 281 | justify-content: center; 282 | width: 28px; 283 | height: 28px; 284 | padding: 4px; 285 | border-radius: 9999px; 286 | color: var(--gray11); 287 | 288 | & svg { 289 | width: 16px; 290 | height: 16px; 291 | } 292 | 293 | &[aria-selected] { 294 | background: #ffffff; 295 | color: var(--gray12); 296 | box-shadow: 297 | 0px 2px 5px -2px rgb(0 0 0 / 15%), 298 | 0 1px 3px -1px rgb(0 0 0 / 20%); 299 | } 300 | } 301 | } 302 | 303 | .versionBadge { 304 | display: inline-flex; 305 | align-items: center; 306 | justify-content: center; 307 | color: var(--grayA11); 308 | background: var(--grayA3); 309 | padding: 4px 8px; 310 | border-radius: 4px; 311 | font-weight: 500; 312 | font-size: 14px; 313 | margin-bottom: 8px; 314 | 315 | @media (prefers-color-scheme: dark) { 316 | background: var(--grayA2); 317 | } 318 | } 319 | 320 | .codeBlock { 321 | margin-top: 72px; 322 | position: relative; 323 | } 324 | 325 | .footer { 326 | display: flex; 327 | flex-direction: column; 328 | align-items: center; 329 | gap: 4px; 330 | width: fit-content; 331 | margin: 32px auto; 332 | bottom: 16px; 333 | color: var(--gray11); 334 | font-size: 13px; 335 | z-index: 10; 336 | position: absolute; 337 | bottom: 0; 338 | 339 | & a { 340 | display: inline-flex; 341 | align-items: center; 342 | gap: 4px; 343 | color: var(--gray12); 344 | font-weight: 500; 345 | border-radius: 9999px; 346 | padding: 4px; 347 | margin: 0 -2px; 348 | transition: background 150ms ease; 349 | 350 | &:hover, 351 | &:focus-visible { 352 | background: var(--grayA4); 353 | outline: 0; 354 | } 355 | } 356 | 357 | & img { 358 | width: 20px; 359 | height: 20px; 360 | border: 1px solid var(--gray5); 361 | border-radius: 9999px; 362 | } 363 | } 364 | 365 | .line { 366 | height: 20px; 367 | width: 180px; 368 | margin: 64px auto; 369 | background-image: url('/line.svg'); 370 | filter: invert(1); 371 | mask-image: linear-gradient(90deg, transparent, #fff 4rem, #fff calc(100% - 4rem), transparent); 372 | 373 | @media (prefers-color-scheme: dark) { 374 | filter: unset; 375 | } 376 | } 377 | 378 | .line2 { 379 | height: 1px; 380 | width: 300px; 381 | background: var(--gray7); 382 | position: absolute; 383 | top: 0; 384 | mask-image: linear-gradient(90deg, transparent, #fff 4rem, #fff calc(100% - 4rem), transparent); 385 | } 386 | 387 | .line3 { 388 | height: 300px; 389 | width: calc(100% + 32px); 390 | position: absolute; 391 | top: -16px; 392 | left: -16px; 393 | 394 | border-radius: 16px 16px 0 0; 395 | --size: 1px; 396 | --gradient: linear-gradient(to top, var(--gray1), var(--gray7)); 397 | 398 | &:before { 399 | content: ''; 400 | position: absolute; 401 | inset: 0; 402 | border-radius: inherit; 403 | padding: var(--size); 404 | background: linear-gradient(to top, var(--gray1), var(--gray7)); 405 | mask: 406 | linear-gradient(black, black) content-box, 407 | linear-gradient(black, black); 408 | mask-composite: exclude; 409 | transform: translateZ(0); 410 | } 411 | 412 | @media (prefers-color-scheme: dark) { 413 | &:before { 414 | mask: none; 415 | mask-composite: none; 416 | opacity: 0.2; 417 | backdrop-filter: blur(20px); 418 | } 419 | } 420 | } 421 | 422 | .raunoSignature, 423 | .pacoSignature { 424 | position: absolute; 425 | height: fit-content; 426 | color: var(--gray11); 427 | pointer-events: none; 428 | } 429 | 430 | .raunoSignature { 431 | width: 120px; 432 | stroke-dashoffset: 1; 433 | stroke-dasharray: 1; 434 | right: -48px; 435 | } 436 | 437 | .pacoSignature { 438 | width: 120px; 439 | stroke-dashoffset: 1; 440 | stroke-dasharray: 1; 441 | left: -8px; 442 | } 443 | 444 | .footerText { 445 | display: flex; 446 | display: flex; 447 | align-items: center; 448 | gap: 4px; 449 | } 450 | 451 | .footer[data-animate='true'] { 452 | .raunoSignature path { 453 | animation: drawRaunoSignature 1.5s ease forwards 0.5s; 454 | } 455 | 456 | .pacoSignature path { 457 | animation: drawPacoSignature 0.8s linear forwards 0.5s; 458 | } 459 | 460 | .footerText { 461 | animation: showFooter 1s linear forwards 3s; 462 | } 463 | } 464 | 465 | @keyframes drawPacoSignature { 466 | 100% { 467 | stroke-dashoffset: 0; 468 | } 469 | } 470 | 471 | @keyframes drawRaunoSignature { 472 | 100% { 473 | stroke-dashoffset: 0; 474 | } 475 | } 476 | 477 | @keyframes showFooter { 478 | 100% { 479 | opacity: 1; 480 | } 481 | } 482 | 483 | @media (max-width: 640px) { 484 | .main { 485 | padding-top: 24px; 486 | padding-bottom: 120px; 487 | } 488 | 489 | .switcher { 490 | grid-template-columns: repeat(2, 100px); 491 | gap: 16px; 492 | 493 | .arrow { 494 | display: none; 495 | } 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /src/styles/cmdk/framer.postcss: -------------------------------------------------------------------------------- 1 | .framer { 2 | [data-cmdk-root] { 3 | max-width: 640px; 4 | width: 100%; 5 | padding: 8px; 6 | background: #ffffff; 7 | border-radius: 16px; 8 | overflow: hidden; 9 | font-family: var(--font-sans); 10 | border: 1px solid var(--gray6); 11 | box-shadow: var(--cmdk-shadow); 12 | 13 | .dark & { 14 | background: var(--gray2); 15 | } 16 | } 17 | 18 | [data-cmdk-framer-header] { 19 | display: flex; 20 | align-items: center; 21 | gap: 8px; 22 | height: 48px; 23 | padding: 0 8px; 24 | border-bottom: 1px solid var(--gray5); 25 | margin-bottom: 12px; 26 | padding-bottom: 8px; 27 | 28 | & svg { 29 | width: 20px; 30 | height: 20px; 31 | color: var(--gray9); 32 | transform: translateY(1px); 33 | } 34 | } 35 | 36 | [data-cmdk-input] { 37 | font-family: var(--font-sans); 38 | border: none; 39 | width: 100%; 40 | font-size: 16px; 41 | outline: none; 42 | background: var(--bg); 43 | color: var(--gray12); 44 | 45 | &::placeholder { 46 | color: var(--gray9); 47 | } 48 | } 49 | 50 | [data-cmdk-item] { 51 | content-visibility: auto; 52 | 53 | cursor: pointer; 54 | border-radius: 12px; 55 | font-size: 14px; 56 | display: flex; 57 | align-items: center; 58 | gap: 12px; 59 | color: var(--gray12); 60 | padding: 8px 8px; 61 | margin-right: 8px; 62 | font-weight: 500; 63 | transition: all 150ms ease; 64 | transition-property: none; 65 | 66 | &[data-selected='true'] { 67 | background: var(--blue9); 68 | color: #ffffff; 69 | 70 | [data-cmdk-framer-item-subtitle] { 71 | color: #ffffff; 72 | } 73 | } 74 | 75 | &[data-disabled='true'] { 76 | color: var(--gray8); 77 | cursor: not-allowed; 78 | } 79 | 80 | & + [data-cmdk-item] { 81 | margin-top: 4px; 82 | } 83 | 84 | & svg { 85 | width: 16px; 86 | height: 16px; 87 | color: #ffffff; 88 | } 89 | } 90 | 91 | [data-cmdk-framer-icon-wrapper] { 92 | display: flex; 93 | align-items: center; 94 | justify-content: center; 95 | min-width: 32px; 96 | height: 32px; 97 | background: orange; 98 | border-radius: 8px; 99 | } 100 | 101 | [data-cmdk-framer-item-meta] { 102 | display: flex; 103 | flex-direction: column; 104 | gap: 4px; 105 | } 106 | 107 | [data-cmdk-framer-item-subtitle] { 108 | font-size: 12px; 109 | font-weight: 400; 110 | color: var(--gray11); 111 | } 112 | 113 | [data-cmdk-framer-items] { 114 | min-height: 308px; 115 | display: flex; 116 | } 117 | 118 | [data-cmdk-framer-left] { 119 | width: 40%; 120 | } 121 | 122 | [data-cmdk-framer-separator] { 123 | width: 1px; 124 | border: 0; 125 | margin-right: 8px; 126 | background: var(--gray6); 127 | } 128 | 129 | [data-cmdk-framer-right] { 130 | display: flex; 131 | align-items: center; 132 | justify-content: center; 133 | border-radius: 8px; 134 | margin-left: 8px; 135 | width: 60%; 136 | 137 | & button { 138 | width: 120px; 139 | height: 40px; 140 | background: var(--blue9); 141 | border-radius: 6px; 142 | font-weight: 500; 143 | color: white; 144 | font-size: 14px; 145 | } 146 | 147 | & input[type='text'] { 148 | height: 40px; 149 | width: 160px; 150 | border: 1px solid var(--gray6); 151 | background: #ffffff; 152 | border-radius: 6px; 153 | padding: 0 8px; 154 | font-size: 14px; 155 | font-family: var(--font-sans); 156 | box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.08); 157 | 158 | &::placeholder { 159 | color: var(--gray9); 160 | } 161 | } 162 | 163 | [data-cmdk-framer-radio] { 164 | display: flex; 165 | align-items: center; 166 | gap: 4px; 167 | color: var(--gray12); 168 | font-weight: 500; 169 | font-size: 14px; 170 | accent-color: var(--blue9); 171 | 172 | & input { 173 | width: 20px; 174 | height: 20px; 175 | } 176 | } 177 | 178 | & img { 179 | width: 40px; 180 | height: 40px; 181 | border-radius: 9999px; 182 | border: 1px solid var(--gray6); 183 | } 184 | 185 | [data-cmdk-framer-container] { 186 | width: 100px; 187 | height: 100px; 188 | background: var(--blue9); 189 | border-radius: 16px; 190 | } 191 | 192 | [data-cmdk-framer-badge] { 193 | background: var(--blue3); 194 | padding: 0 8px; 195 | height: 28px; 196 | font-size: 14px; 197 | line-height: 28px; 198 | color: var(--blue11); 199 | border-radius: 9999px; 200 | font-weight: 500; 201 | } 202 | 203 | [data-cmdk-framer-slider] { 204 | height: 20px; 205 | width: 200px; 206 | background: linear-gradient(90deg, var(--blue9) 40%, var(--gray3) 0%); 207 | border-radius: 9999px; 208 | 209 | & div { 210 | width: 20px; 211 | height: 20px; 212 | background: #ffffff; 213 | border-radius: 9999px; 214 | box-shadow: 0 1px 3px -1px rgba(0, 0, 0, 0.32); 215 | transform: translateX(70px); 216 | } 217 | } 218 | } 219 | 220 | [data-cmdk-list] { 221 | overflow: auto; 222 | } 223 | 224 | [data-cmdk-separator] { 225 | height: 1px; 226 | width: 100%; 227 | background: var(--gray5); 228 | margin: 4px 0; 229 | } 230 | 231 | [data-cmdk-group-heading] { 232 | user-select: none; 233 | font-size: 12px; 234 | color: var(--gray11); 235 | padding: 0 8px; 236 | display: flex; 237 | align-items: center; 238 | margin-bottom: 8px; 239 | } 240 | 241 | [data-cmdk-empty] { 242 | font-size: 14px; 243 | padding: 32px; 244 | white-space: pre-wrap; 245 | color: var(--gray11); 246 | } 247 | } 248 | 249 | @media (max-width: 640px) { 250 | .framer { 251 | [data-cmdk-framer-icon-wrapper] { 252 | } 253 | 254 | [data-cmdk-framer-item-subtitle] { 255 | display: none; 256 | } 257 | } 258 | } 259 | 260 | @media (prefers-color-scheme: dark) { 261 | .framer { 262 | [data-cmdk-framer-right] { 263 | & input[type='text'] { 264 | background: var(--gray3); 265 | } 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/styles/cmdk/linear.postcss: -------------------------------------------------------------------------------- 1 | .linear { 2 | [data-cmdk-root] { 3 | max-width: 640px; 4 | width: 100%; 5 | background: #ffffff; 6 | border-radius: 8px; 7 | overflow: hidden; 8 | padding: 0; 9 | font-family: var(--font-sans); 10 | box-shadow: var(--cmdk-shadow); 11 | 12 | .dark & { 13 | background: linear-gradient(136.61deg, rgb(39, 40, 43) 13.72%, rgb(45, 46, 49) 74.3%); 14 | } 15 | } 16 | 17 | [data-cmdk-linear-badge] { 18 | height: 24px; 19 | padding: 0 8px; 20 | font-size: 12px; 21 | color: var(--gray11); 22 | background: var(--gray3); 23 | border-radius: 4px; 24 | width: fit-content; 25 | display: flex; 26 | align-items: center; 27 | margin: 16px 16px 0; 28 | } 29 | 30 | [data-cmdk-linear-shortcuts] { 31 | display: flex; 32 | margin-left: auto; 33 | gap: 8px; 34 | 35 | & kbd { 36 | font-family: var(--font-sans); 37 | font-size: 13px; 38 | color: var(--gray11); 39 | } 40 | } 41 | 42 | [data-cmdk-input] { 43 | font-family: var(--font-sans); 44 | border: none; 45 | width: 100%; 46 | font-size: 18px; 47 | padding: 20px; 48 | outline: none; 49 | background: var(--bg); 50 | color: var(--gray12); 51 | border-bottom: 1px solid var(--gray6); 52 | border-radius: 0; 53 | caret-color: #6e5ed2; 54 | margin: 0; 55 | 56 | &::placeholder { 57 | color: var(--gray9); 58 | } 59 | } 60 | 61 | [data-cmdk-item] { 62 | content-visibility: auto; 63 | 64 | cursor: pointer; 65 | height: 48px; 66 | font-size: 14px; 67 | display: flex; 68 | align-items: center; 69 | gap: 12px; 70 | padding: 0 16px; 71 | color: var(--gray12); 72 | user-select: none; 73 | will-change: background, color; 74 | transition: all 150ms ease; 75 | transition-property: none; 76 | position: relative; 77 | 78 | &[data-selected='true'] { 79 | background: var(--gray3); 80 | 81 | svg { 82 | color: var(--gray12); 83 | } 84 | 85 | &:after { 86 | content: ''; 87 | position: absolute; 88 | left: 0; 89 | z-index: 123; 90 | width: 3px; 91 | height: 100%; 92 | background: #5f6ad2; 93 | } 94 | } 95 | 96 | &[data-disabled='true'] { 97 | color: var(--gray8); 98 | cursor: not-allowed; 99 | } 100 | 101 | &:active { 102 | transition-property: background; 103 | background: var(--gray4); 104 | } 105 | 106 | & + [data-cmdk-item] { 107 | margin-top: 4px; 108 | } 109 | 110 | & svg { 111 | width: 16px; 112 | height: 16px; 113 | color: var(--gray10); 114 | } 115 | } 116 | 117 | [data-cmdk-list] { 118 | height: min(300px, var(--cmdk-list-height)); 119 | max-height: 400px; 120 | overflow: auto; 121 | overscroll-behavior: contain; 122 | transition: 100ms ease; 123 | transition-property: height; 124 | } 125 | 126 | [data-cmdk-group-heading] { 127 | user-select: none; 128 | font-size: 12px; 129 | color: var(--gray11); 130 | padding: 0 8px; 131 | display: flex; 132 | align-items: center; 133 | } 134 | 135 | [data-cmdk-empty] { 136 | font-size: 14px; 137 | display: flex; 138 | align-items: center; 139 | justify-content: center; 140 | height: 64px; 141 | white-space: pre-wrap; 142 | color: var(--gray11); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/styles/cmdk/raycast.postcss: -------------------------------------------------------------------------------- 1 | .raycast { 2 | [data-cmdk-root] { 3 | max-width: 640px; 4 | width: 100%; 5 | background: var(--gray1); 6 | border-radius: 12px; 7 | padding: 8px 0; 8 | font-family: var(--font-sans); 9 | box-shadow: var(--cmdk-shadow); 10 | border: 1px solid var(--gray6); 11 | position: relative; 12 | 13 | .dark & { 14 | background: var(--gray2); 15 | border: 0; 16 | 17 | &:after { 18 | content: ''; 19 | background: linear-gradient( 20 | to right, 21 | var(--gray6) 20%, 22 | var(--gray6) 40%, 23 | var(--gray10) 50%, 24 | var(--gray10) 55%, 25 | var(--gray6) 70%, 26 | var(--gray6) 100% 27 | ); 28 | z-index: -1; 29 | position: absolute; 30 | border-radius: 12px; 31 | top: -1px; 32 | left: -1px; 33 | width: calc(100% + 2px); 34 | height: calc(100% + 2px); 35 | animation: shine 3s ease forwards 0.1s; 36 | background-size: 200% auto; 37 | } 38 | 39 | &:before { 40 | content: ''; 41 | z-index: -1; 42 | position: absolute; 43 | border-radius: 12px; 44 | top: -1px; 45 | left: -1px; 46 | width: calc(100% + 2px); 47 | height: calc(100% + 2px); 48 | box-shadow: 0 0 0 1px transparent; 49 | animation: border 1s linear forwards 0.5s; 50 | } 51 | } 52 | 53 | & kbd { 54 | font-family: var(--font-sans); 55 | background: var(--gray3); 56 | color: var(--gray11); 57 | height: 20px; 58 | width: 20px; 59 | border-radius: 4px; 60 | padding: 0 4px; 61 | display: flex; 62 | align-items: center; 63 | justify-content: center; 64 | 65 | &:first-of-type { 66 | margin-left: 8px; 67 | } 68 | } 69 | } 70 | 71 | [data-cmdk-input] { 72 | font-family: var(--font-sans); 73 | border: none; 74 | width: 100%; 75 | font-size: 15px; 76 | padding: 8px 16px; 77 | outline: none; 78 | background: var(--bg); 79 | color: var(--gray12); 80 | 81 | &::placeholder { 82 | color: var(--gray9); 83 | } 84 | } 85 | 86 | [data-cmdk-raycast-top-shine] { 87 | .dark & { 88 | background: linear-gradient( 89 | 90deg, 90 | rgba(56, 189, 248, 0), 91 | var(--gray5) 20%, 92 | var(--gray9) 67.19%, 93 | rgba(236, 72, 153, 0) 94 | ); 95 | height: 1px; 96 | position: absolute; 97 | top: -1px; 98 | width: 100%; 99 | z-index: -1; 100 | opacity: 0; 101 | animation: showTopShine 0.1s ease forwards 0.2s; 102 | } 103 | } 104 | 105 | [data-cmdk-raycast-loader] { 106 | --loader-color: var(--gray9); 107 | border: 0; 108 | width: 100%; 109 | width: 100%; 110 | left: 0; 111 | height: 1px; 112 | background: var(--gray6); 113 | position: relative; 114 | overflow: visible; 115 | display: block; 116 | margin-top: 12px; 117 | margin-bottom: 12px; 118 | 119 | &:after { 120 | content: ''; 121 | width: 50%; 122 | height: 1px; 123 | position: absolute; 124 | background: linear-gradient(90deg, transparent 0%, var(--loader-color) 50%, transparent 100%); 125 | top: -1px; 126 | opacity: 0; 127 | animation-duration: 1.5s; 128 | animation-delay: 1s; 129 | animation-timing-function: ease; 130 | animation-name: loading; 131 | } 132 | } 133 | 134 | [data-cmdk-item] { 135 | content-visibility: auto; 136 | 137 | cursor: pointer; 138 | height: 40px; 139 | border-radius: 8px; 140 | font-size: 14px; 141 | display: flex; 142 | align-items: center; 143 | gap: 8px; 144 | padding: 0 8px; 145 | color: var(--gray12); 146 | user-select: none; 147 | will-change: background, color; 148 | transition: all 150ms ease; 149 | transition-property: none; 150 | 151 | &[data-selected='true'] { 152 | background: var(--gray4); 153 | color: var(--gray12); 154 | } 155 | 156 | &[data-disabled='true'] { 157 | color: var(--gray8); 158 | cursor: not-allowed; 159 | } 160 | 161 | &:active { 162 | transition-property: background; 163 | background: var(--gray4); 164 | } 165 | 166 | &:first-child { 167 | margin-top: 8px; 168 | } 169 | 170 | & + [data-cmdk-item] { 171 | margin-top: 4px; 172 | } 173 | 174 | & svg { 175 | width: 18px; 176 | height: 18px; 177 | } 178 | } 179 | 180 | [data-cmdk-raycast-meta] { 181 | margin-left: auto; 182 | color: var(--gray11); 183 | font-size: 13px; 184 | } 185 | 186 | [data-cmdk-list] { 187 | padding: 0 8px; 188 | height: 393px; 189 | overflow: auto; 190 | overscroll-behavior: contain; 191 | scroll-padding-block-end: 40px; 192 | transition: 100ms ease; 193 | transition-property: height; 194 | padding-bottom: 40px; 195 | } 196 | 197 | [data-cmdk-raycast-open-trigger], 198 | [data-cmdk-raycast-subcommand-trigger] { 199 | color: var(--gray11); 200 | padding: 0px 4px 0px 8px; 201 | border-radius: 6px; 202 | font-weight: 500; 203 | font-size: 12px; 204 | height: 28px; 205 | letter-spacing: -0.25px; 206 | } 207 | 208 | [data-cmdk-raycast-clipboard-icon], 209 | [data-cmdk-raycast-hammer-icon] { 210 | width: 20px; 211 | height: 20px; 212 | border-radius: 6px; 213 | display: flex; 214 | align-items: center; 215 | justify-content: center; 216 | color: #ffffff; 217 | 218 | & svg { 219 | width: 14px; 220 | height: 14px; 221 | } 222 | } 223 | 224 | [data-cmdk-raycast-clipboard-icon] { 225 | background: linear-gradient(to bottom, #f55354, #eb4646); 226 | } 227 | 228 | [data-cmdk-raycast-hammer-icon] { 229 | background: linear-gradient(to bottom, #6cb9a3, #2c6459); 230 | } 231 | 232 | [data-cmdk-raycast-open-trigger] { 233 | display: flex; 234 | align-items: center; 235 | color: var(--gray12); 236 | } 237 | 238 | [data-cmdk-raycast-subcommand-trigger] { 239 | display: flex; 240 | align-items: center; 241 | gap: 4px; 242 | right: 8px; 243 | bottom: 8px; 244 | 245 | & svg { 246 | width: 14px; 247 | height: 14px; 248 | } 249 | 250 | & hr { 251 | height: 100%; 252 | background: var(--gray6); 253 | border: 0; 254 | width: 1px; 255 | } 256 | 257 | &[aria-expanded='true'], 258 | &:hover { 259 | background: var(--gray4); 260 | 261 | & kbd { 262 | background: var(--gray7); 263 | } 264 | } 265 | } 266 | 267 | [data-cmdk-separator] { 268 | height: 1px; 269 | width: 100%; 270 | background: var(--gray5); 271 | margin: 4px 0; 272 | } 273 | 274 | & *:not([hidden]) + [data-cmdk-group] { 275 | margin-top: 8px; 276 | } 277 | 278 | [data-cmdk-group-heading] { 279 | user-select: none; 280 | font-size: 12px; 281 | color: var(--gray11); 282 | padding: 0 8px; 283 | display: flex; 284 | align-items: center; 285 | } 286 | 287 | [data-cmdk-raycast-footer] { 288 | display: flex; 289 | height: 40px; 290 | align-items: center; 291 | width: 100%; 292 | position: absolute; 293 | background: var(--gray1); 294 | bottom: 0; 295 | padding: 8px; 296 | border-top: 1px solid var(--gray6); 297 | border-radius: 0 0 12px 12px; 298 | z-index: 2; 299 | 300 | & svg { 301 | width: 20px; 302 | height: 20px; 303 | filter: grayscale(1); 304 | margin-right: auto; 305 | } 306 | 307 | & hr { 308 | height: 12px; 309 | width: 1px; 310 | border: 0; 311 | background: var(--gray6); 312 | margin: 0 4px 0px 12px; 313 | } 314 | } 315 | 316 | [data-cmdk-dialog] { 317 | z-index: var(--layer-portal); 318 | position: fixed; 319 | left: 50%; 320 | top: var(--page-top); 321 | transform: translateX(-50%); 322 | 323 | [data-cmdk] { 324 | width: 640px; 325 | transform-origin: center center; 326 | animation: dialogIn var(--transition-fast) forwards; 327 | } 328 | 329 | &[data-state='closed'] [data-cmdk] { 330 | animation: dialogOut var(--transition-fast) forwards; 331 | } 332 | } 333 | 334 | [data-cmdk-empty] { 335 | font-size: 14px; 336 | display: flex; 337 | align-items: center; 338 | justify-content: center; 339 | height: 64px; 340 | white-space: pre-wrap; 341 | color: var(--gray11); 342 | } 343 | } 344 | 345 | @keyframes loading { 346 | 0% { 347 | opacity: 0; 348 | transform: translateX(0); 349 | } 350 | 351 | 50% { 352 | opacity: 1; 353 | transform: translateX(100%); 354 | } 355 | 356 | 100% { 357 | opacity: 0; 358 | transform: translateX(0); 359 | } 360 | } 361 | 362 | @keyframes shine { 363 | to { 364 | background-position: 200% center; 365 | opacity: 0; 366 | } 367 | } 368 | 369 | @keyframes border { 370 | to { 371 | box-shadow: 0 0 0 1px var(--gray6); 372 | } 373 | } 374 | 375 | @keyframes showTopShine { 376 | to { 377 | opacity: 1; 378 | } 379 | } 380 | 381 | .raycast-submenu { 382 | z-index: 50; 383 | 384 | [data-cmdk-root] { 385 | display: flex; 386 | flex-direction: column; 387 | width: 320px; 388 | border: 1px solid var(--gray6); 389 | background: var(--gray2); 390 | border-radius: 8px; 391 | } 392 | 393 | [data-cmdk-list] { 394 | padding: 8px; 395 | overflow: auto; 396 | overscroll-behavior: contain; 397 | transition: 100ms ease; 398 | transition-property: height; 399 | } 400 | 401 | [data-cmdk-item] { 402 | height: 40px; 403 | 404 | cursor: pointer; 405 | height: 40px; 406 | border-radius: 8px; 407 | font-size: 13px; 408 | display: flex; 409 | align-items: center; 410 | gap: 8px; 411 | padding: 0 8px; 412 | color: var(--gray12); 413 | user-select: none; 414 | will-change: background, color; 415 | transition: all 150ms ease; 416 | transition-property: none; 417 | 418 | &[aria-selected='true'] { 419 | background: var(--gray5); 420 | color: var(--gray12); 421 | 422 | [data-cmdk-raycast-submenu-shortcuts] kbd { 423 | background: var(--gray7); 424 | } 425 | } 426 | 427 | &[aria-disabled='true'] { 428 | color: var(--gray8); 429 | cursor: not-allowed; 430 | } 431 | 432 | & svg { 433 | width: 16px; 434 | height: 16px; 435 | } 436 | 437 | [data-cmdk-raycast-submenu-shortcuts] { 438 | display: flex; 439 | margin-left: auto; 440 | gap: 2px; 441 | 442 | & kbd { 443 | font-family: var(--font-sans); 444 | background: var(--gray5); 445 | color: var(--gray11); 446 | height: 20px; 447 | width: 20px; 448 | border-radius: 4px; 449 | padding: 0 4px; 450 | font-size: 12px; 451 | display: flex; 452 | align-items: center; 453 | justify-content: center; 454 | 455 | &:first-of-type { 456 | margin-left: 8px; 457 | } 458 | } 459 | } 460 | } 461 | 462 | [data-cmdk-group-heading] { 463 | text-transform: capitalize; 464 | font-size: 12px; 465 | color: var(--gray11); 466 | font-weight: 500; 467 | margin-bottom: 8px; 468 | margin-top: 8px; 469 | margin-left: 4px; 470 | } 471 | 472 | [data-cmdk-input] { 473 | padding: 12px; 474 | font-family: var(--font-sans); 475 | border: 0; 476 | border-top: 1px solid var(--gray6); 477 | font-size: 13px; 478 | background: transparent; 479 | margin-top: auto; 480 | width: 100%; 481 | outline: 0; 482 | border-radius: 0; 483 | } 484 | 485 | animation-duration: 0.2s; 486 | animation-timing-function: ease; 487 | animation-fill-mode: forwards; 488 | transform-origin: var(--radix-popover-content-transform-origin); 489 | 490 | &[data-state='open'] { 491 | animation-name: slideIn; 492 | } 493 | 494 | &[data-state='closed'] { 495 | animation-name: slideOut; 496 | } 497 | 498 | [data-cmdk-empty] { 499 | display: flex; 500 | align-items: center; 501 | justify-content: center; 502 | height: 64px; 503 | white-space: pre-wrap; 504 | font-size: 14px; 505 | color: var(--gray11); 506 | } 507 | } 508 | 509 | @keyframes slideIn { 510 | 0% { 511 | opacity: 0; 512 | transform: scale(0.96); 513 | } 514 | 515 | 100% { 516 | opacity: 1; 517 | transform: scale(1); 518 | } 519 | } 520 | 521 | @keyframes slideOut { 522 | 0% { 523 | opacity: 1; 524 | transform: scale(1); 525 | } 526 | 527 | 100% { 528 | opacity: 0; 529 | transform: scale(0.96); 530 | } 531 | } 532 | 533 | @media (max-width: 640px) { 534 | .raycast { 535 | [data-cmdk-input] { 536 | font-size: 16px; 537 | } 538 | } 539 | } 540 | 541 | @media (prefers-color-scheme: dark) { 542 | .raycast { 543 | [data-cmdk-raycast-footer] { 544 | background: var(--gray2); 545 | } 546 | } 547 | } 548 | -------------------------------------------------------------------------------- /src/styles/cmdk/vercel.postcss: -------------------------------------------------------------------------------- 1 | .vercel { 2 | [data-cmdk-root] { 3 | max-width: 640px; 4 | width: 100%; 5 | padding: 8px; 6 | background: #ffffff; 7 | border-radius: 12px; 8 | overflow: hidden; 9 | font-family: var(--font-sans); 10 | border: 1px solid var(--gray6); 11 | box-shadow: var(--cmdk-shadow); 12 | transition: transform 100ms ease; 13 | 14 | .dark & { 15 | background: rgba(22, 22, 22, 0.7); 16 | } 17 | } 18 | 19 | [data-cmdk-input] { 20 | font-family: var(--font-sans); 21 | border: none; 22 | width: 100%; 23 | font-size: 17px; 24 | padding: 8px 8px 16px 8px; 25 | outline: none; 26 | background: var(--bg); 27 | color: var(--gray12); 28 | border-bottom: 1px solid var(--gray6); 29 | margin-bottom: 16px; 30 | border-radius: 0; 31 | 32 | &::placeholder { 33 | color: var(--gray9); 34 | } 35 | } 36 | 37 | [data-cmdk-vercel-badge] { 38 | height: 20px; 39 | background: var(--grayA3); 40 | display: inline-flex; 41 | align-items: center; 42 | padding: 0 8px; 43 | font-size: 12px; 44 | color: var(--grayA11); 45 | border-radius: 4px; 46 | margin: 4px 0 4px 4px; 47 | user-select: none; 48 | text-transform: capitalize; 49 | font-weight: 500; 50 | } 51 | 52 | [data-cmdk-item] { 53 | content-visibility: auto; 54 | 55 | cursor: pointer; 56 | height: 48px; 57 | border-radius: 8px; 58 | font-size: 14px; 59 | display: flex; 60 | align-items: center; 61 | gap: 8px; 62 | padding: 0 16px; 63 | color: var(--gray11); 64 | user-select: none; 65 | will-change: background, color; 66 | transition: all 150ms ease; 67 | transition-property: none; 68 | 69 | &[data-selected='true'] { 70 | background: var(--grayA3); 71 | color: var(--gray12); 72 | } 73 | 74 | &[data-disabled='true'] { 75 | color: var(--gray8); 76 | cursor: not-allowed; 77 | } 78 | 79 | &:active { 80 | transition-property: background; 81 | background: var(--gray4); 82 | } 83 | 84 | & + [data-cmdk-item] { 85 | margin-top: 4px; 86 | } 87 | 88 | svg { 89 | width: 18px; 90 | height: 18px; 91 | } 92 | } 93 | 94 | [data-cmdk-list] { 95 | height: min(330px, calc(var(--cmdk-list-height))); 96 | max-height: 400px; 97 | overflow: auto; 98 | overscroll-behavior: contain; 99 | transition: 100ms ease; 100 | transition-property: height; 101 | } 102 | 103 | [data-cmdk-vercel-shortcuts] { 104 | display: flex; 105 | margin-left: auto; 106 | gap: 8px; 107 | 108 | & kbd { 109 | font-family: var(--font-sans); 110 | font-size: 12px; 111 | min-width: 20px; 112 | padding: 4px; 113 | height: 20px; 114 | border-radius: 4px; 115 | color: var(--gray11); 116 | background: var(--gray4); 117 | display: inline-flex; 118 | align-items: center; 119 | justify-content: center; 120 | text-transform: uppercase; 121 | } 122 | } 123 | 124 | [data-cmdk-separator] { 125 | height: 1px; 126 | width: 100%; 127 | background: var(--gray5); 128 | margin: 4px 0; 129 | } 130 | 131 | *:not([hidden]) + [data-cmdk-group] { 132 | margin-top: 8px; 133 | } 134 | 135 | [data-cmdk-group-heading] { 136 | user-select: none; 137 | font-size: 12px; 138 | color: var(--gray11); 139 | padding: 0 8px; 140 | display: flex; 141 | align-items: center; 142 | margin-bottom: 8px; 143 | } 144 | 145 | [data-cmdk-empty] { 146 | font-size: 14px; 147 | display: flex; 148 | align-items: center; 149 | justify-content: center; 150 | height: 48px; 151 | white-space: pre-wrap; 152 | color: var(--gray11); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/styles/code.postcss: -------------------------------------------------------------------------------- 1 | .root { 2 | color: var(--gray12); 3 | border-radius: 12px; 4 | padding: 16px; 5 | backdrop-filter: blur(10px); 6 | border: 1px solid var(--gray6); 7 | position: relative; 8 | line-height: 16px; 9 | background: var(--lowContrast); 10 | white-space: pre-wrap; 11 | box-shadow: rgb(0 0 0 / 10%) 0px 5px 30px -5px; 12 | display: flex; 13 | flex-direction: column; 14 | 15 | @media (prefers-color-scheme: dark) { 16 | background: var(--grayA2); 17 | } 18 | 19 | & button { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | width: 32px; 24 | height: 32px; 25 | background: var(--grayA3); 26 | border-radius: 8px; 27 | position: absolute; 28 | top: 12px; 29 | right: 12px; 30 | color: var(--gray11); 31 | cursor: copy; 32 | transition: 33 | color 150ms ease, 34 | background 150ms ease, 35 | transform 150ms ease; 36 | 37 | &:hover { 38 | color: var(--gray12); 39 | background: var(--grayA4); 40 | } 41 | 42 | &:active { 43 | color: var(--gray12); 44 | background: var(--grayA5); 45 | transform: scale(0.96); 46 | } 47 | } 48 | 49 | .token { 50 | padding-right: -1.25rem; 51 | } 52 | } 53 | 54 | @media (prefers-color-scheme: dark) { 55 | .shine { 56 | background: linear-gradient( 57 | 90deg, 58 | rgba(56, 189, 248, 0), 59 | var(--gray5) 20%, 60 | var(--gray9) 67.19%, 61 | rgba(236, 72, 153, 0) 62 | ); 63 | height: 1px; 64 | position: absolute; 65 | top: -1px; 66 | width: 97%; 67 | z-index: -1; 68 | } 69 | } 70 | 71 | @media (max-width: 640px) { 72 | .root { 73 | :global(.token-line) { 74 | font-size: 11px !important; 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * a11y-dark theme for JavaScript, CSS, and HTML 81 | * Based on the okaidia theme: https://github.com/PrismJS/prism/blob/gh-pages/themes/prism-okaidia.css 82 | * @author ericwbailey 83 | */ 84 | 85 | /* Not Mine */ 86 | 87 | .token.class-name, 88 | .token.function, 89 | .token.tag { 90 | color: var(--gray12); 91 | } 92 | 93 | .token.selector, 94 | .token.attr-name, 95 | .token.string, 96 | .token.char, 97 | .token.builtin, 98 | .token.inserted { 99 | color: var(--gray12); 100 | } 101 | 102 | .token.important, 103 | .token.bold { 104 | font-weight: bold; 105 | } 106 | 107 | .token.italic { 108 | font-style: italic; 109 | } 110 | 111 | .token.entity { 112 | cursor: help; 113 | } 114 | 115 | /* Mine */ 116 | .token.comment { 117 | color: var(--gray9); 118 | } 119 | 120 | .token.atrule, 121 | .token.keyword, 122 | .token.attr-name, 123 | .token.selector { 124 | color: var(--gray10); 125 | } 126 | 127 | .token.punctuation, 128 | .token.operator { 129 | color: var(--gray9); 130 | } 131 | -------------------------------------------------------------------------------- /src/styles/globals.postcss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Inter'; 3 | font-style: normal; 4 | font-weight: 100 900; 5 | font-display: optional; 6 | src: url(/inter-var-latin.woff2) format('woff2'); 7 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, 8 | U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 9 | } 10 | 11 | ::selection { 12 | background: #eb5027; 13 | color: white; 14 | } 15 | 16 | html, 17 | body { 18 | padding: 0; 19 | margin: 0; 20 | font-family: var(--font-sans); 21 | } 22 | 23 | body { 24 | background: var(--app-bg); 25 | overflow-x: hidden; 26 | } 27 | 28 | button { 29 | background: none; 30 | font-family: var(--font-sans); 31 | padding: 0; 32 | border: 0; 33 | } 34 | 35 | h1, 36 | h2, 37 | h3, 38 | h4, 39 | h5, 40 | h6, 41 | p { 42 | margin: 0; 43 | } 44 | 45 | a { 46 | color: inherit; 47 | text-decoration: none; 48 | } 49 | 50 | *, 51 | *::after, 52 | *::before { 53 | box-sizing: border-box; 54 | -webkit-font-smoothing: antialiased; 55 | -moz-osx-font-smoothing: grayscale; 56 | } 57 | 58 | :root { 59 | --font-sans: 'Inter', --apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, 60 | Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 61 | --app-bg: var(--gray1); 62 | --cmdk-shadow: 0 16px 70px rgb(0 0 0 / 20%); 63 | 64 | --lowContrast: #ffffff; 65 | --highContrast: #000000; 66 | 67 | --gray1: hsl(0, 0%, 99%); 68 | --gray2: hsl(0, 0%, 97.3%); 69 | --gray3: hsl(0, 0%, 95.1%); 70 | --gray4: hsl(0, 0%, 93%); 71 | --gray5: hsl(0, 0%, 90.9%); 72 | --gray6: hsl(0, 0%, 88.7%); 73 | --gray7: hsl(0, 0%, 85.8%); 74 | --gray8: hsl(0, 0%, 78%); 75 | --gray9: hsl(0, 0%, 56.1%); 76 | --gray10: hsl(0, 0%, 52.3%); 77 | --gray11: hsl(0, 0%, 43.5%); 78 | --gray12: hsl(0, 0%, 9%); 79 | 80 | --grayA1: hsla(0, 0%, 0%, 0.012); 81 | --grayA2: hsla(0, 0%, 0%, 0.027); 82 | --grayA3: hsla(0, 0%, 0%, 0.047); 83 | --grayA4: hsla(0, 0%, 0%, 0.071); 84 | --grayA5: hsla(0, 0%, 0%, 0.09); 85 | --grayA6: hsla(0, 0%, 0%, 0.114); 86 | --grayA7: hsla(0, 0%, 0%, 0.141); 87 | --grayA8: hsla(0, 0%, 0%, 0.22); 88 | --grayA9: hsla(0, 0%, 0%, 0.439); 89 | --grayA10: hsla(0, 0%, 0%, 0.478); 90 | --grayA11: hsla(0, 0%, 0%, 0.565); 91 | --grayA12: hsla(0, 0%, 0%, 0.91); 92 | 93 | --blue1: hsl(206, 100%, 99.2%); 94 | --blue2: hsl(210, 100%, 98%); 95 | --blue3: hsl(209, 100%, 96.5%); 96 | --blue4: hsl(210, 98.8%, 94%); 97 | --blue5: hsl(209, 95%, 90.1%); 98 | --blue6: hsl(209, 81.2%, 84.5%); 99 | --blue7: hsl(208, 77.5%, 76.9%); 100 | --blue8: hsl(206, 81.9%, 65.3%); 101 | --blue9: hsl(206, 100%, 50%); 102 | --blue10: hsl(208, 100%, 47.3%); 103 | --blue11: hsl(211, 100%, 43.2%); 104 | --blue12: hsl(211, 100%, 15%); 105 | } 106 | 107 | .dark { 108 | --app-bg: var(--gray1); 109 | 110 | --lowContrast: #000000; 111 | --highContrast: #ffffff; 112 | 113 | --gray1: hsl(0, 0%, 8.5%); 114 | --gray2: hsl(0, 0%, 11%); 115 | --gray3: hsl(0, 0%, 13.6%); 116 | --gray4: hsl(0, 0%, 15.8%); 117 | --gray5: hsl(0, 0%, 17.9%); 118 | --gray6: hsl(0, 0%, 20.5%); 119 | --gray7: hsl(0, 0%, 24.3%); 120 | --gray8: hsl(0, 0%, 31.2%); 121 | --gray9: hsl(0, 0%, 43.9%); 122 | --gray10: hsl(0, 0%, 49.4%); 123 | --gray11: hsl(0, 0%, 62.8%); 124 | --gray12: hsl(0, 0%, 93%); 125 | 126 | --grayA1: hsla(0, 0%, 100%, 0); 127 | --grayA2: hsla(0, 0%, 100%, 0.026); 128 | --grayA3: hsla(0, 0%, 100%, 0.056); 129 | --grayA4: hsla(0, 0%, 100%, 0.077); 130 | --grayA5: hsla(0, 0%, 100%, 0.103); 131 | --grayA6: hsla(0, 0%, 100%, 0.129); 132 | --grayA7: hsla(0, 0%, 100%, 0.172); 133 | --grayA8: hsla(0, 0%, 100%, 0.249); 134 | --grayA9: hsla(0, 0%, 100%, 0.386); 135 | --grayA10: hsla(0, 0%, 100%, 0.446); 136 | --grayA11: hsla(0, 0%, 100%, 0.592); 137 | --grayA12: hsla(0, 0%, 100%, 0.923); 138 | 139 | --blue1: hsl(212, 35%, 9.2%); 140 | --blue2: hsl(216, 50%, 11.8%); 141 | --blue3: hsl(214, 59.4%, 15.3%); 142 | --blue4: hsl(214, 65.8%, 17.9%); 143 | --blue5: hsl(213, 71.2%, 20.2%); 144 | --blue6: hsl(212, 77.4%, 23.1%); 145 | --blue7: hsl(211, 85.1%, 27.4%); 146 | --blue8: hsl(211, 89.7%, 34.1%); 147 | --blue9: hsl(206, 100%, 50%); 148 | --blue10: hsl(209, 100%, 60.6%); 149 | --blue11: hsl(210, 100%, 66.1%); 150 | --blue12: hsl(206, 98%, 95.8%); 151 | } 152 | -------------------------------------------------------------------------------- /src/styles/icons.postcss: -------------------------------------------------------------------------------- 1 | .blurLogo { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | position: relative; 6 | border-radius: 4px; 7 | overflow: hidden; 8 | box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, 0.015); 9 | 10 | .bg { 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | position: absolute; 15 | z-index: 1; 16 | pointer-events: none; 17 | user-select: none; 18 | top: 0; 19 | left: 0; 20 | width: 100%; 21 | height: 100%; 22 | transform: scale(1.5) translateZ(0); 23 | filter: blur(12px) opacity(0.4) saturate(100%); 24 | transition: filter 150ms ease; 25 | } 26 | 27 | .inner { 28 | display: flex; 29 | align-items: center; 30 | justify-content: center; 31 | object-fit: cover; 32 | width: 100%; 33 | height: 100%; 34 | user-select: none; 35 | pointer-events: none; 36 | border-radius: inherit; 37 | z-index: 2; 38 | 39 | & svg { 40 | width: 14px; 41 | height: 14px; 42 | filter: drop-shadow(0 4px 4px rgba(0, 0, 0, 0.16)); 43 | transition: filter 150ms ease; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /static/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /static/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/huntabyte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huntabyte/cmdk-sv/aff9a23797ebd02402194c3caa9acfabcf80eabc/static/huntabyte.png -------------------------------------------------------------------------------- /static/inter-var-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huntabyte/cmdk-sv/aff9a23797ebd02402194c3caa9acfabcf80eabc/static/inter-var-latin.woff2 -------------------------------------------------------------------------------- /static/line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huntabyte/cmdk-sv/aff9a23797ebd02402194c3caa9acfabcf80eabc/static/og.png -------------------------------------------------------------------------------- /static/paco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huntabyte/cmdk-sv/aff9a23797ebd02402194c3caa9acfabcf80eabc/static/paco.png -------------------------------------------------------------------------------- /static/rauno.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huntabyte/cmdk-sv/aff9a23797ebd02402194c3caa9acfabcf80eabc/static/rauno.jpeg -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /static/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-vercel'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: [vitePreprocess()], 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter(), 15 | alias: { 16 | $docs: './src/docs', 17 | $styles: './src/styles' 18 | } 19 | } 20 | }; 21 | 22 | export default config; 23 | -------------------------------------------------------------------------------- /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 | await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible(); 6 | }); 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "NodeNext", 13 | "module": "NodeNext" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | test: { 7 | include: ['src/**/*.{test,spec}.{js,ts}'] 8 | }, 9 | server: { 10 | fs: { 11 | allow: ['package.json'] 12 | } 13 | } 14 | }); 15 | --------------------------------------------------------------------------------