@salmexio/ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dialogs/ContextMenu/ContextMenu.svelte +521 -0
- package/dist/dialogs/ContextMenu/ContextMenu.svelte.d.ts +53 -0
- package/dist/dialogs/ContextMenu/ContextMenu.svelte.d.ts.map +1 -0
- package/dist/dialogs/ContextMenu/index.d.ts +3 -0
- package/dist/dialogs/ContextMenu/index.d.ts.map +1 -0
- package/dist/dialogs/ContextMenu/index.js +1 -0
- package/dist/dialogs/index.d.ts +2 -0
- package/dist/dialogs/index.d.ts.map +1 -1
- package/dist/dialogs/index.js +1 -0
- package/dist/feedback/Alert/Alert.svelte +1 -62
- package/dist/feedback/Alert/Alert.svelte.d.ts +1 -1
- package/dist/feedback/Alert/Alert.svelte.d.ts.map +1 -1
- package/dist/forms/Select/Select.svelte +883 -0
- package/dist/forms/Select/Select.svelte.d.ts +68 -0
- package/dist/forms/Select/Select.svelte.d.ts.map +1 -0
- package/dist/forms/Select/index.d.ts +3 -0
- package/dist/forms/Select/index.d.ts.map +1 -0
- package/dist/forms/Select/index.js +1 -0
- package/dist/forms/index.d.ts +2 -0
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +1 -0
- package/dist/layout/Card/Card.svelte +29 -169
- package/dist/layout/Card/Card.svelte.d.ts +3 -9
- package/dist/layout/Card/Card.svelte.d.ts.map +1 -1
- package/dist/navigation/CommandPalette/CommandPalette.svelte +574 -0
- package/dist/navigation/CommandPalette/CommandPalette.svelte.d.ts +47 -0
- package/dist/navigation/CommandPalette/CommandPalette.svelte.d.ts.map +1 -0
- package/dist/navigation/CommandPalette/index.d.ts +3 -0
- package/dist/navigation/CommandPalette/index.d.ts.map +1 -0
- package/dist/navigation/CommandPalette/index.js +1 -0
- package/dist/navigation/index.d.ts +2 -0
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +1 -0
- package/dist/primitives/Badge/Badge.svelte +45 -9
- package/dist/primitives/Badge/Badge.svelte.d.ts +0 -2
- package/dist/primitives/Badge/Badge.svelte.d.ts.map +1 -1
- package/dist/primitives/Button/Button.svelte +40 -14
- package/dist/primitives/Button/Button.svelte.d.ts +1 -1
- package/dist/primitives/Button/Button.svelte.d.ts.map +1 -1
- package/dist/styles/tokens.css +4 -4
- package/dist/windowing/Window/Window.svelte +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component CommandPalette
|
|
3
|
+
|
|
4
|
+
Win2K × Basquiat — Keyboard-first command launcher with sunken search field,
|
|
5
|
+
categorised result list, fuzzy matching, and shortcut display.
|
|
6
|
+
|
|
7
|
+
@example
|
|
8
|
+
<CommandPalette
|
|
9
|
+
commands={[
|
|
10
|
+
{ id: 'save', label: 'Save File', shortcut: 'Ctrl+S', group: 'File', action: handleSave },
|
|
11
|
+
{ id: 'open', label: 'Open File', shortcut: 'Ctrl+O', group: 'File', action: handleOpen },
|
|
12
|
+
]}
|
|
13
|
+
bind:open={showPalette}
|
|
14
|
+
/>
|
|
15
|
+
-->
|
|
16
|
+
<script lang="ts" module>
|
|
17
|
+
export interface CommandItem {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
shortcut?: string;
|
|
21
|
+
group?: string;
|
|
22
|
+
icon?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
action: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CommandGroup {
|
|
29
|
+
label: string;
|
|
30
|
+
items: CommandItem[];
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<script lang="ts">
|
|
35
|
+
import { cn } from '../../utils/cn.js';
|
|
36
|
+
import { Keys, createFocusTrap } from '../../utils/keyboard.js';
|
|
37
|
+
import { onMount, tick } from 'svelte';
|
|
38
|
+
import type { FocusTrap } from '../../utils/keyboard.js';
|
|
39
|
+
|
|
40
|
+
interface Props {
|
|
41
|
+
/** All available commands */
|
|
42
|
+
commands: CommandItem[];
|
|
43
|
+
/** Whether the palette is open */
|
|
44
|
+
open?: boolean;
|
|
45
|
+
/** Placeholder for the search field */
|
|
46
|
+
placeholder?: string;
|
|
47
|
+
/** Additional CSS class */
|
|
48
|
+
class?: string;
|
|
49
|
+
/** Called when palette closes */
|
|
50
|
+
onclose?: () => void;
|
|
51
|
+
/** Test ID */
|
|
52
|
+
testId?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let {
|
|
56
|
+
commands,
|
|
57
|
+
open = $bindable(false),
|
|
58
|
+
placeholder = 'Type a command...',
|
|
59
|
+
class: className = '',
|
|
60
|
+
onclose,
|
|
61
|
+
testId
|
|
62
|
+
}: Props = $props();
|
|
63
|
+
|
|
64
|
+
let query = $state('');
|
|
65
|
+
let activeIndex = $state(0);
|
|
66
|
+
let inputEl = $state<HTMLInputElement | null>(null);
|
|
67
|
+
let panelEl = $state<HTMLDivElement | null>(null);
|
|
68
|
+
let listEl = $state<HTMLDivElement | null>(null);
|
|
69
|
+
let backdropEl = $state<HTMLDivElement | null>(null);
|
|
70
|
+
let wrapperEl = $state<HTMLDivElement | null>(null);
|
|
71
|
+
let focusTrap: FocusTrap | null = null;
|
|
72
|
+
|
|
73
|
+
// Fuzzy match: check if query chars appear in order in the label
|
|
74
|
+
function fuzzyMatch(text: string, search: string): boolean {
|
|
75
|
+
if (!search) return true;
|
|
76
|
+
const lower = text.toLowerCase();
|
|
77
|
+
const searchLower = search.toLowerCase();
|
|
78
|
+
let si = 0;
|
|
79
|
+
for (let ti = 0; ti < lower.length && si < searchLower.length; ti++) {
|
|
80
|
+
if (lower[ti] === searchLower[si]) si++;
|
|
81
|
+
}
|
|
82
|
+
return si === searchLower.length;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Score: prefer starts-with and shorter labels
|
|
86
|
+
function fuzzyScore(text: string, search: string): number {
|
|
87
|
+
if (!search) return 0;
|
|
88
|
+
const lower = text.toLowerCase();
|
|
89
|
+
const searchLower = search.toLowerCase();
|
|
90
|
+
let score = 0;
|
|
91
|
+
if (lower.startsWith(searchLower)) score += 100;
|
|
92
|
+
if (lower.includes(searchLower)) score += 50;
|
|
93
|
+
score -= text.length;
|
|
94
|
+
return score;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const filteredCommands = $derived<CommandItem[]>(
|
|
98
|
+
commands
|
|
99
|
+
.filter((c) => fuzzyMatch(c.label, query) || (c.description && fuzzyMatch(c.description, query)))
|
|
100
|
+
.sort((a, b) => fuzzyScore(b.label, query) - fuzzyScore(a.label, query))
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Group filtered results
|
|
104
|
+
const groupedResults = $derived.by(() => {
|
|
105
|
+
const groups = new Map<string, CommandItem[]>();
|
|
106
|
+
for (const item of filteredCommands) {
|
|
107
|
+
const key = item.group ?? '';
|
|
108
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
109
|
+
groups.get(key)!.push(item);
|
|
110
|
+
}
|
|
111
|
+
return Array.from(groups.entries()).map(([label, items]) => ({ label, items })) as CommandGroup[];
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Flat list for keyboard navigation
|
|
115
|
+
const flatFiltered = $derived(filteredCommands);
|
|
116
|
+
|
|
117
|
+
function openPalette() {
|
|
118
|
+
open = true;
|
|
119
|
+
query = '';
|
|
120
|
+
activeIndex = 0;
|
|
121
|
+
requestAnimationFrame(() => {
|
|
122
|
+
inputEl?.focus();
|
|
123
|
+
if (panelEl) {
|
|
124
|
+
focusTrap = createFocusTrap(panelEl);
|
|
125
|
+
focusTrap.activate();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function closePalette() {
|
|
131
|
+
open = false;
|
|
132
|
+
query = '';
|
|
133
|
+
activeIndex = 0;
|
|
134
|
+
focusTrap?.deactivate();
|
|
135
|
+
focusTrap = null;
|
|
136
|
+
onclose?.();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function executeItem(item: CommandItem) {
|
|
140
|
+
if (item.disabled) return;
|
|
141
|
+
closePalette();
|
|
142
|
+
item.action();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function scrollActiveIntoView() {
|
|
146
|
+
requestAnimationFrame(() => {
|
|
147
|
+
if (!listEl) return;
|
|
148
|
+
const active = listEl.querySelector('[data-active="true"]') as HTMLElement;
|
|
149
|
+
if (!active) return;
|
|
150
|
+
const listTop = listEl.scrollTop;
|
|
151
|
+
const listHeight = listEl.clientHeight;
|
|
152
|
+
const elTop = active.offsetTop;
|
|
153
|
+
const elHeight = active.offsetHeight;
|
|
154
|
+
if (elTop < listTop) {
|
|
155
|
+
listEl.scrollTop = elTop;
|
|
156
|
+
} else if (elTop + elHeight > listTop + listHeight) {
|
|
157
|
+
listEl.scrollTop = elTop + elHeight - listHeight;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
163
|
+
switch (e.key) {
|
|
164
|
+
case Keys.ArrowDown:
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
activeIndex = Math.min(activeIndex + 1, flatFiltered.length - 1);
|
|
167
|
+
scrollActiveIntoView();
|
|
168
|
+
break;
|
|
169
|
+
case Keys.ArrowUp:
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
activeIndex = Math.max(activeIndex - 1, 0);
|
|
172
|
+
scrollActiveIntoView();
|
|
173
|
+
break;
|
|
174
|
+
case Keys.Enter:
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
if (flatFiltered[activeIndex]) executeItem(flatFiltered[activeIndex]);
|
|
177
|
+
break;
|
|
178
|
+
case Keys.Escape:
|
|
179
|
+
e.preventDefault();
|
|
180
|
+
closePalette();
|
|
181
|
+
break;
|
|
182
|
+
case Keys.Home:
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
activeIndex = 0;
|
|
185
|
+
scrollActiveIntoView();
|
|
186
|
+
break;
|
|
187
|
+
case Keys.End:
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
activeIndex = Math.max(flatFiltered.length - 1, 0);
|
|
190
|
+
scrollActiveIntoView();
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function handleInput() {
|
|
196
|
+
activeIndex = 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function handleBackdropClick() {
|
|
200
|
+
closePalette();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Watch for external open changes
|
|
204
|
+
$effect(() => {
|
|
205
|
+
if (open) openPalette();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Portal: move wrapper to document.body to escape transform/overflow ancestors
|
|
209
|
+
$effect(() => {
|
|
210
|
+
if (wrapperEl && open) {
|
|
211
|
+
document.body.appendChild(wrapperEl);
|
|
212
|
+
return () => {
|
|
213
|
+
if (wrapperEl?.parentNode === document.body) {
|
|
214
|
+
document.body.removeChild(wrapperEl);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Global keyboard shortcut: Ctrl+K / Cmd+K
|
|
221
|
+
onMount(() => {
|
|
222
|
+
function onGlobalKey(e: KeyboardEvent) {
|
|
223
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
if (open) closePalette();
|
|
226
|
+
else openPalette();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
document.addEventListener('keydown', onGlobalKey);
|
|
230
|
+
return () => {
|
|
231
|
+
document.removeEventListener('keydown', onGlobalKey);
|
|
232
|
+
focusTrap?.deactivate();
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Track cumulative index for items within grouped display
|
|
237
|
+
function getCumulativeIndex(groupIdx: number, itemIdx: number): number {
|
|
238
|
+
let offset = 0;
|
|
239
|
+
for (let g = 0; g < groupIdx; g++) {
|
|
240
|
+
offset += groupedResults[g].items.length;
|
|
241
|
+
}
|
|
242
|
+
return offset + itemIdx;
|
|
243
|
+
}
|
|
244
|
+
</script>
|
|
245
|
+
|
|
246
|
+
{#if open}
|
|
247
|
+
<div bind:this={wrapperEl}>
|
|
248
|
+
<!-- Backdrop -->
|
|
249
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
250
|
+
<div bind:this={backdropEl} class="salmex-cmd-backdrop" onclick={handleBackdropClick} onkeydown={() => {}}></div>
|
|
251
|
+
|
|
252
|
+
<!-- Panel -->
|
|
253
|
+
<div
|
|
254
|
+
bind:this={panelEl}
|
|
255
|
+
class={cn('salmex-cmd', className)}
|
|
256
|
+
role="dialog"
|
|
257
|
+
tabindex="-1"
|
|
258
|
+
aria-label="Command palette"
|
|
259
|
+
data-testid={testId}
|
|
260
|
+
onkeydown={handleKeydown}
|
|
261
|
+
>
|
|
262
|
+
<!-- Search field -->
|
|
263
|
+
<div class="salmex-cmd-search">
|
|
264
|
+
<span class="salmex-cmd-search-icon" aria-hidden="true">
|
|
265
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
|
|
266
|
+
<circle cx="6.5" cy="6.5" r="5" />
|
|
267
|
+
<path d="M10 10L14.5 14.5" />
|
|
268
|
+
</svg>
|
|
269
|
+
</span>
|
|
270
|
+
<input
|
|
271
|
+
bind:this={inputEl}
|
|
272
|
+
type="text"
|
|
273
|
+
class="salmex-cmd-input"
|
|
274
|
+
{placeholder}
|
|
275
|
+
bind:value={query}
|
|
276
|
+
oninput={handleInput}
|
|
277
|
+
aria-label="Search commands"
|
|
278
|
+
aria-controls="cmd-results"
|
|
279
|
+
aria-activedescendant={flatFiltered[activeIndex] ? `cmd-item-${flatFiltered[activeIndex].id}` : undefined}
|
|
280
|
+
autocomplete="off"
|
|
281
|
+
spellcheck="false"
|
|
282
|
+
/>
|
|
283
|
+
<span class="salmex-cmd-shortcut-hint" aria-hidden="true">ESC</span>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<!-- Results -->
|
|
287
|
+
<div bind:this={listEl} id="cmd-results" class="salmex-cmd-results" role="listbox">
|
|
288
|
+
{#if flatFiltered.length === 0}
|
|
289
|
+
<div class="salmex-cmd-empty">No commands found.</div>
|
|
290
|
+
{:else}
|
|
291
|
+
{#each groupedResults as group, gi}
|
|
292
|
+
{#if group.label}
|
|
293
|
+
<div class="salmex-cmd-group-label">{group.label}</div>
|
|
294
|
+
{/if}
|
|
295
|
+
{#each group.items as item, ii}
|
|
296
|
+
{@const globalIdx = getCumulativeIndex(gi, ii)}
|
|
297
|
+
{@const isActive = globalIdx === activeIndex}
|
|
298
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions a11y_click_events_have_key_events -->
|
|
299
|
+
<div
|
|
300
|
+
id="cmd-item-{item.id}"
|
|
301
|
+
class={cn(
|
|
302
|
+
'salmex-cmd-item',
|
|
303
|
+
isActive && 'salmex-cmd-item-active',
|
|
304
|
+
item.disabled && 'salmex-cmd-item-disabled'
|
|
305
|
+
)}
|
|
306
|
+
role="option"
|
|
307
|
+
tabindex="-1"
|
|
308
|
+
aria-selected={isActive}
|
|
309
|
+
aria-disabled={item.disabled || undefined}
|
|
310
|
+
data-active={isActive}
|
|
311
|
+
onmouseenter={() => { activeIndex = globalIdx; }}
|
|
312
|
+
onclick={() => executeItem(item)}
|
|
313
|
+
onkeydown={handleKeydown}
|
|
314
|
+
>
|
|
315
|
+
{#if item.icon}
|
|
316
|
+
<span class="salmex-cmd-item-icon" aria-hidden="true">{item.icon}</span>
|
|
317
|
+
{/if}
|
|
318
|
+
<div class="salmex-cmd-item-content">
|
|
319
|
+
<span class="salmex-cmd-item-label">{item.label}</span>
|
|
320
|
+
{#if item.description}
|
|
321
|
+
<span class="salmex-cmd-item-desc">{item.description}</span>
|
|
322
|
+
{/if}
|
|
323
|
+
</div>
|
|
324
|
+
{#if item.shortcut}
|
|
325
|
+
<span class="salmex-cmd-item-shortcut">{item.shortcut}</span>
|
|
326
|
+
{/if}
|
|
327
|
+
</div>
|
|
328
|
+
{/each}
|
|
329
|
+
{/each}
|
|
330
|
+
{/if}
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<!-- Footer hint -->
|
|
334
|
+
<div class="salmex-cmd-footer">
|
|
335
|
+
<span><kbd>↑↓</kbd> navigate</span>
|
|
336
|
+
<span><kbd>↵</kbd> select</span>
|
|
337
|
+
<span><kbd>esc</kbd> close</span>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
{/if}
|
|
342
|
+
|
|
343
|
+
<style>
|
|
344
|
+
/* ========================================
|
|
345
|
+
BACKDROP
|
|
346
|
+
======================================== */
|
|
347
|
+
.salmex-cmd-backdrop {
|
|
348
|
+
position: fixed;
|
|
349
|
+
inset: 0;
|
|
350
|
+
z-index: var(--salmex-z-modal-backdrop);
|
|
351
|
+
background: rgb(0 0 0 / 0.4);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* ========================================
|
|
355
|
+
PANEL
|
|
356
|
+
======================================== */
|
|
357
|
+
.salmex-cmd {
|
|
358
|
+
position: fixed;
|
|
359
|
+
top: 20%;
|
|
360
|
+
left: 50%;
|
|
361
|
+
transform: translateX(-50%);
|
|
362
|
+
z-index: var(--salmex-z-modal);
|
|
363
|
+
width: min(560px, calc(100vw - 32px));
|
|
364
|
+
max-height: 420px;
|
|
365
|
+
display: flex;
|
|
366
|
+
flex-direction: column;
|
|
367
|
+
background: rgb(var(--salmex-window-surface));
|
|
368
|
+
border: 3px solid rgb(var(--salmex-border-dark));
|
|
369
|
+
box-shadow:
|
|
370
|
+
inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
|
|
371
|
+
inset -1px -1px 0 rgb(var(--salmex-button-shadow)),
|
|
372
|
+
8px 8px 0 rgb(0 0 0 / 0.4);
|
|
373
|
+
font-family: var(--salmex-font-system);
|
|
374
|
+
overflow: hidden;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
:global([data-theme='dark']) .salmex-cmd {
|
|
378
|
+
box-shadow:
|
|
379
|
+
inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
|
|
380
|
+
inset -1px -1px 0 rgb(var(--salmex-button-shadow)),
|
|
381
|
+
8px 8px 0 rgb(0 0 0 / 0.7);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* ========================================
|
|
385
|
+
SEARCH FIELD — Sunken, prominent
|
|
386
|
+
======================================== */
|
|
387
|
+
.salmex-cmd-search {
|
|
388
|
+
display: flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
gap: var(--salmex-space-3);
|
|
391
|
+
padding: var(--salmex-space-4) var(--salmex-space-5);
|
|
392
|
+
border-bottom: 1px solid rgb(var(--salmex-border-default));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.salmex-cmd-search-icon {
|
|
396
|
+
flex-shrink: 0;
|
|
397
|
+
display: flex;
|
|
398
|
+
color: rgb(var(--salmex-text-secondary));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.salmex-cmd-input {
|
|
402
|
+
flex: 1;
|
|
403
|
+
border: none;
|
|
404
|
+
background: transparent;
|
|
405
|
+
outline: none;
|
|
406
|
+
font-family: var(--salmex-font-system);
|
|
407
|
+
font-size: var(--salmex-font-size-md);
|
|
408
|
+
font-weight: 600;
|
|
409
|
+
color: rgb(var(--salmex-text-primary));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.salmex-cmd-input::placeholder {
|
|
413
|
+
color: rgb(var(--salmex-text-disabled));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.salmex-cmd-shortcut-hint {
|
|
417
|
+
flex-shrink: 0;
|
|
418
|
+
font-family: var(--salmex-font-mono);
|
|
419
|
+
font-size: var(--salmex-font-size-xs);
|
|
420
|
+
font-weight: 700;
|
|
421
|
+
padding: 2px 6px;
|
|
422
|
+
border: 2px solid rgb(var(--salmex-border-default));
|
|
423
|
+
background: rgb(var(--salmex-button-face));
|
|
424
|
+
color: rgb(var(--salmex-text-secondary));
|
|
425
|
+
box-shadow:
|
|
426
|
+
inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
|
|
427
|
+
inset -1px -1px 0 rgb(var(--salmex-button-shadow));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* ========================================
|
|
431
|
+
RESULTS LIST
|
|
432
|
+
======================================== */
|
|
433
|
+
.salmex-cmd-results {
|
|
434
|
+
flex: 1;
|
|
435
|
+
overflow-y: auto;
|
|
436
|
+
padding: var(--salmex-space-1) 0;
|
|
437
|
+
max-height: 300px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.salmex-cmd-empty {
|
|
441
|
+
padding: var(--salmex-space-6) var(--salmex-space-5);
|
|
442
|
+
text-align: center;
|
|
443
|
+
font-size: var(--salmex-font-size-sm);
|
|
444
|
+
color: rgb(var(--salmex-text-disabled));
|
|
445
|
+
font-weight: 600;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* ========================================
|
|
449
|
+
GROUP LABEL
|
|
450
|
+
======================================== */
|
|
451
|
+
.salmex-cmd-group-label {
|
|
452
|
+
padding: var(--salmex-space-2) var(--salmex-space-5) var(--salmex-space-1);
|
|
453
|
+
margin-top: var(--salmex-space-1);
|
|
454
|
+
font-family: var(--salmex-font-mono);
|
|
455
|
+
font-size: 10px;
|
|
456
|
+
font-weight: 600;
|
|
457
|
+
text-transform: uppercase;
|
|
458
|
+
letter-spacing: 0.8px;
|
|
459
|
+
color: rgb(var(--salmex-text-disabled));
|
|
460
|
+
border-top: 1px solid rgb(var(--salmex-border-light));
|
|
461
|
+
user-select: none;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.salmex-cmd-group-label:first-child {
|
|
465
|
+
border-top: none;
|
|
466
|
+
margin-top: 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/* ========================================
|
|
470
|
+
COMMAND ITEM
|
|
471
|
+
======================================== */
|
|
472
|
+
.salmex-cmd-item {
|
|
473
|
+
display: flex;
|
|
474
|
+
align-items: center;
|
|
475
|
+
gap: var(--salmex-space-3);
|
|
476
|
+
padding: var(--salmex-space-2) var(--salmex-space-5);
|
|
477
|
+
cursor: pointer;
|
|
478
|
+
user-select: none;
|
|
479
|
+
transition: background var(--salmex-transition-fast);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.salmex-cmd-item-active {
|
|
483
|
+
background: rgb(var(--salmex-electric-blue));
|
|
484
|
+
color: rgb(var(--salmex-chalk-white));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
:global([data-theme='dark']) .salmex-cmd-item-active {
|
|
488
|
+
background: rgb(var(--salmex-primary-light));
|
|
489
|
+
color: rgb(var(--salmex-midnight-black));
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.salmex-cmd-item-disabled {
|
|
493
|
+
opacity: 0.4;
|
|
494
|
+
cursor: not-allowed;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.salmex-cmd-item-icon {
|
|
498
|
+
flex-shrink: 0;
|
|
499
|
+
width: 20px;
|
|
500
|
+
text-align: center;
|
|
501
|
+
font-size: 16px;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.salmex-cmd-item-content {
|
|
505
|
+
flex: 1;
|
|
506
|
+
min-width: 0;
|
|
507
|
+
display: flex;
|
|
508
|
+
flex-direction: column;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.salmex-cmd-item-label {
|
|
512
|
+
font-size: var(--salmex-font-size-sm);
|
|
513
|
+
font-weight: 700;
|
|
514
|
+
overflow: hidden;
|
|
515
|
+
text-overflow: ellipsis;
|
|
516
|
+
white-space: nowrap;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.salmex-cmd-item-desc {
|
|
520
|
+
font-size: var(--salmex-font-size-xs);
|
|
521
|
+
opacity: 0.7;
|
|
522
|
+
overflow: hidden;
|
|
523
|
+
text-overflow: ellipsis;
|
|
524
|
+
white-space: nowrap;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.salmex-cmd-item-active .salmex-cmd-item-desc {
|
|
528
|
+
opacity: 0.85;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.salmex-cmd-item-shortcut {
|
|
532
|
+
flex-shrink: 0;
|
|
533
|
+
font-family: var(--salmex-font-mono);
|
|
534
|
+
font-size: var(--salmex-font-size-xs);
|
|
535
|
+
font-weight: 600;
|
|
536
|
+
opacity: 0.6;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.salmex-cmd-item-active .salmex-cmd-item-shortcut {
|
|
540
|
+
opacity: 0.85;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/* ========================================
|
|
544
|
+
FOOTER
|
|
545
|
+
======================================== */
|
|
546
|
+
.salmex-cmd-footer {
|
|
547
|
+
display: flex;
|
|
548
|
+
align-items: center;
|
|
549
|
+
gap: var(--salmex-space-5);
|
|
550
|
+
padding: var(--salmex-space-2) var(--salmex-space-5);
|
|
551
|
+
border-top: 1px solid rgb(var(--salmex-border-default));
|
|
552
|
+
font-size: var(--salmex-font-size-xs);
|
|
553
|
+
color: rgb(var(--salmex-text-secondary));
|
|
554
|
+
background: rgb(var(--salmex-window-surface));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.salmex-cmd-footer kbd {
|
|
558
|
+
font-family: var(--salmex-font-mono);
|
|
559
|
+
font-weight: 700;
|
|
560
|
+
padding: 1px 4px;
|
|
561
|
+
border: 1px solid rgb(var(--salmex-border-default));
|
|
562
|
+
background: rgb(var(--salmex-button-face));
|
|
563
|
+
font-size: 0.9em;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/* ========================================
|
|
567
|
+
REDUCED MOTION
|
|
568
|
+
======================================== */
|
|
569
|
+
@media (prefers-reduced-motion: reduce) {
|
|
570
|
+
.salmex-cmd-item {
|
|
571
|
+
transition: none;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
</style>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface CommandItem {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
shortcut?: string;
|
|
5
|
+
group?: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
action: () => void;
|
|
10
|
+
}
|
|
11
|
+
export interface CommandGroup {
|
|
12
|
+
label: string;
|
|
13
|
+
items: CommandItem[];
|
|
14
|
+
}
|
|
15
|
+
interface Props {
|
|
16
|
+
/** All available commands */
|
|
17
|
+
commands: CommandItem[];
|
|
18
|
+
/** Whether the palette is open */
|
|
19
|
+
open?: boolean;
|
|
20
|
+
/** Placeholder for the search field */
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
/** Additional CSS class */
|
|
23
|
+
class?: string;
|
|
24
|
+
/** Called when palette closes */
|
|
25
|
+
onclose?: () => void;
|
|
26
|
+
/** Test ID */
|
|
27
|
+
testId?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* CommandPalette
|
|
31
|
+
*
|
|
32
|
+
* Win2K × Basquiat — Keyboard-first command launcher with sunken search field,
|
|
33
|
+
* categorised result list, fuzzy matching, and shortcut display.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* <CommandPalette
|
|
37
|
+
* commands={[
|
|
38
|
+
* { id: 'save', label: 'Save File', shortcut: 'Ctrl+S', group: 'File', action: handleSave },
|
|
39
|
+
* { id: 'open', label: 'Open File', shortcut: 'Ctrl+O', group: 'File', action: handleOpen },
|
|
40
|
+
* ]}
|
|
41
|
+
* bind:open={showPalette}
|
|
42
|
+
* />
|
|
43
|
+
*/
|
|
44
|
+
declare const CommandPalette: import("svelte").Component<Props, {}, "open">;
|
|
45
|
+
type CommandPalette = ReturnType<typeof CommandPalette>;
|
|
46
|
+
export default CommandPalette;
|
|
47
|
+
//# sourceMappingURL=CommandPalette.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPalette.svelte.d.ts","sourceRoot":"","sources":["../../../src/navigation/CommandPalette/CommandPalette.svelte.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,WAAW,EAAE,CAAC;CACrB;AASD,UAAU,KAAK;IACd,6BAA6B;IAC7B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,kCAAkC;IAClC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,cAAc;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAgRD;;;;;;;;;;;;;;GAcG;AACH,QAAA,MAAM,cAAc,+CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/navigation/CommandPalette/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CommandPalette } from './CommandPalette.svelte';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/navigation/index.js
CHANGED