Something went wrong. Try again.
small gleam coding and persistent agent daemon with a detachable cli and a canvas ui forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146<script lang="ts" module> export interface Item { key: string; kind: 'command' | 'session' | 'artifact'; label: string; detail?: string; hint?: string; run: () => void; }</script>
<script lang="ts"> import { onDestroy, untrack } from 'svelte'; import { keepFocus } from './popover'; import SearchCard from './SearchCard.svelte';
// Opens and closes without animation: it is used many times a day and // should feel like part of the keyboard. let { placeholder, search, onclose, start = '' }: { placeholder: string; search: (query: string) => Item[]; onclose: () => void; start?: string } = $props();
let query = $state(untrack(() => start)); let active = $state(0); let list = $state<HTMLElement>(); const items = $derived(search(query)); onDestroy(keepFocus());
$effect(() => { query; active = 0; });
$effect(() => { list?.querySelector(`[data-index="${active}"]`)?.scrollIntoView({ block: 'nearest' }); });
function choose(item: Item | undefined) { if (!item) return; onclose(); item.run(); }
function keydown(event: KeyboardEvent) { const step = { ArrowDown: 1, ArrowUp: -1 }[event.key] ?? (event.ctrlKey ? { n: 1, p: -1 }[event.key] : undefined); if (step) { event.preventDefault(); active = (active + step + items.length) % Math.max(items.length, 1); } else if (event.key === 'Enter') { event.preventDefault(); choose(items[active]); } else if (event.key === 'Escape') { event.preventDefault(); onclose(); } }</script>
<div class="backdrop" role="presentation" onpointerdown={(event) => event.target === event.currentTarget && onclose()}> <div class="palette-wrap" role="dialog" aria-label="Command palette" aria-modal="true"> <SearchCard bind:query bind:list {placeholder} ariaControls="palette-list" ariaActivedescendant={items[active] ? `palette-${active}` : undefined} autofocus large onkeydown={keydown} > {#each items as item, index (item.key)} <button type="button" id="palette-{index}" role="option" aria-selected={index === active} data-index={index} class="item" class:active={index === active} onpointermove={() => (active = index)} onclick={() => choose(item)} > <span class="label">{item.label}</span> {#if item.detail}<span class="detail">{item.detail}</span>{/if} {#if item.hint}<kbd>{item.hint}</kbd>{/if} </button> {:else} <p class="none">nothing matches</p> {/each} </SearchCard> </div></div>
<style> .backdrop { position: fixed; inset: 0; z-index: var(--z-palette); display: flex; align-items: center; justify-content: center; background: var(--scrim); } .palette-wrap { width: min(560px, calc(100vw - 32px)); } .item { display: flex; align-items: center; gap: 10px; width: 100%; height: 34px; padding: 0 10px; color: var(--text); font: inherit; font-size: calc(13px * var(--ui-scale)); text-align: left; background: none; border: 0; border-radius: 7px; cursor: pointer; } .item.active { background: var(--hover); } .label { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .detail { flex: none; max-width: 45%; overflow: hidden; color: var(--muted); font-size: calc(11.5px * var(--ui-scale)); white-space: nowrap; text-overflow: ellipsis; } .none { margin: 10px; color: var(--muted); font-size: calc(13px * var(--ui-scale)); }</style>