<script lang="ts">
import { onMount, setContext } from 'svelte'
import {
createHotkeys,
createHotkey,
formatForDisplay,
getHeldKeys,
getHeldKeyCodesMap,
getIsKeyHeld,
getHotkeyRegistrations,
} from '@tanstack/svelte-hotkeys'
import Tickets from './Tickets.svelte'
import Editor from './Editor.svelte'
import Sequences from './Sequences.svelte'
import Recording from './Recording.svelte'
import Formatting from './Formatting.svelte'
import Hint from './Hint.svelte'
const pages = [
{ path: '/', name: 'Tickets', component: Tickets, hotkey: 'Alt+[Digit1]' },
{
path: '/editor',
name: 'Editor',
component: Editor,
hotkey: 'Alt+[Digit2]',
},
{
path: '/sequences',
name: 'Sequences',
component: Sequences,
hotkey: 'Alt+[Digit3]',
},
{
path: '/recording',
name: 'Recording',
component: Recording,
hotkey: 'Alt+[Digit4]',
},
{
path: '/formatting',
name: 'Formatting',
component: Formatting,
hotkey: 'Alt+[Digit5]',
},
] as const
let path = $state(location.hash.slice(1) || '/'),
show = $state(false),
activity = $state<Array<string>>([])
onMount(() => {
const update = () => {
path = location.hash.slice(1) || '/'
}
window.addEventListener('hashchange', update)
return () => window.removeEventListener('hashchange', update)
})
const page = $derived(pages.find((p) => p.path === path) ?? pages[0])
setContext('log', (message: string) => {
activity = [message, ...activity].slice(0, 6)
})
const keys = getHeldKeys(),
codes = getHeldKeyCodesMap(),
shift = getIsKeyHeld('Shift'),
registry = getHotkeyRegistrations()
createHotkeys(
pages.map((p) => ({
hotkey: p.hotkey,
callback: () => {
location.hash = p.path
},
options: { meta: { name: p.name, group: 'Navigation' } },
})),
{ ignoreInputs: true },
)
createHotkey(
'Alt+Shift+[KeyK]',
() => {
show = !show
},
{ meta: { name: 'Show shortcuts', group: 'Navigation' } },
)
</script>
<header>
<h1>TanStack Hotkeys kitchen sink</h1>
<p>
Hold Alt / Option for hints. Route changes mount and unmount registrations.
</p>
<nav>
{#each pages as p}<a href={`#${p.path}`} class:active={path === p.path}
>{p.name} <Hint hotkey={p.hotkey} /></a
>{/each}
</nav>
<button onclick={() => (show = !show)}
>{show ? 'Hide' : 'Show'} shortcuts</button
>
</header>
{#if show}<section>
<h2>Live registry</h2>
<table>
<thead
><tr><th>Group / action</th><th>Binding</th><th>Status</th></tr></thead
><tbody>
{#each [...registry.hotkeys, ...registry.sequences] as reg (reg.id)}<tr
><td>{reg.options.meta?.group} / {reg.options.meta?.name}</td><td
>{('hotkey' in reg ? [reg.hotkey] : reg.sequence)
.map((h) => formatForDisplay(h))
.join(' → ')}</td
><td
>{reg.options.enabled === false
? 'Disabled'
: `${reg.triggerCount} fired`}</td
></tr
>{/each}</tbody
>
</table>
</section>{/if}
<main>
{#key page.path}<page.component />{/key}
</main>
<section>
<h2>Key state</h2>
<p>
Keys: {keys.keys.join(' + ') || 'None'}<br />Codes: {Object.values(
codes.codes,
).join(' + ') || 'None'}<br />Shift: {shift.held ? 'held' : 'released'}
</p>
<h2>Activity</h2>
<button onclick={() => (activity = [])}>Clear log</button>
<ul aria-live="polite">
{#each activity as message}<li>{message}</li>{/each}
</ul>
</section>