@immich/ui 0.40.3 → 0.41.1
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/components/Card/Card.svelte +1 -1
- package/dist/components/CommandPalette/CommandPaletteContext.svelte +13 -0
- package/dist/components/CommandPalette/CommandPaletteContext.svelte.d.ts +8 -0
- package/dist/components/ContextMenu/ContextMenu.svelte +139 -0
- package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +4 -0
- package/dist/components/Modal/Modal.svelte +4 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/internal/CommandPaletteModal.svelte +131 -0
- package/dist/internal/CommandPaletteModal.svelte.d.ts +8 -0
- package/dist/internal/Select.svelte +2 -1
- package/dist/services/command-palette-manager.svelte.d.ts +36 -12
- package/dist/services/command-palette-manager.svelte.js +120 -37
- package/dist/services/menu-manager.svelte.d.ts +6 -0
- package/dist/services/menu-manager.svelte.js +11 -0
- package/dist/services/modal-manager.svelte.d.ts +7 -7
- package/dist/services/modal-manager.svelte.js +2 -2
- package/dist/types.d.ts +29 -0
- package/dist/types.js +4 -0
- package/package.json +1 -1
- package/dist/components/CommandPalette/CommandPalette.svelte +0 -145
- package/dist/components/CommandPalette/CommandPalette.svelte.d.ts +0 -7
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { commandPaletteManager, type CommandItem } from '../../services/command-palette-manager.svelte';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
commands?: CommandItem[];
|
|
7
|
+
global?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const { commands = [], global }: Props = $props();
|
|
11
|
+
|
|
12
|
+
onMount(() => commandPaletteManager.addCommands(commands, { global }));
|
|
13
|
+
</script>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CommandItem } from '../../services/command-palette-manager.svelte';
|
|
2
|
+
type Props = {
|
|
3
|
+
commands?: CommandItem[];
|
|
4
|
+
global?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const CommandPaletteContext: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type CommandPaletteContext = ReturnType<typeof CommandPaletteContext>;
|
|
8
|
+
export default CommandPaletteContext;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Icon from '../Icon/Icon.svelte';
|
|
3
|
+
import Text from '../Text/Text.svelte';
|
|
4
|
+
import { zIndex } from '../../constants.js';
|
|
5
|
+
import { styleVariants } from '../../styles.js';
|
|
6
|
+
import { MenuItemType, type ContextMenuProps, type MenuItem } from '../../types.js';
|
|
7
|
+
import { cleanClass } from '../../utilities/internal.js';
|
|
8
|
+
import { DropdownMenu } from 'bits-ui';
|
|
9
|
+
import { fly } from 'svelte/transition';
|
|
10
|
+
import { tv } from 'tailwind-variants';
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
onClose,
|
|
14
|
+
items,
|
|
15
|
+
bottomItems,
|
|
16
|
+
size = 'medium',
|
|
17
|
+
anchor,
|
|
18
|
+
position = 'top-left',
|
|
19
|
+
class: className,
|
|
20
|
+
...restProps
|
|
21
|
+
}: ContextMenuProps = $props();
|
|
22
|
+
|
|
23
|
+
const isDivider = (item: MenuItem | MenuItemType): item is MenuItemType => {
|
|
24
|
+
return item === MenuItemType.Divider;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const itemStyles = tv({
|
|
28
|
+
base: 'hover:bg-subtle flex w-full items-center gap-1 rounded-md px-1 py-0.5 text-start',
|
|
29
|
+
variants: {
|
|
30
|
+
color: styleVariants.textColor,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const wrapperStyles = tv({
|
|
35
|
+
base: 'bg-light flex flex-col gap-1 overflow-hidden rounded-lg border py-1',
|
|
36
|
+
variants: {
|
|
37
|
+
size: {
|
|
38
|
+
tiny: 'w-32',
|
|
39
|
+
small: 'w-48',
|
|
40
|
+
medium: 'w-3xs',
|
|
41
|
+
large: 'w-sm',
|
|
42
|
+
giant: 'w-lg',
|
|
43
|
+
full: 'w-full',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const getAlignment = (
|
|
49
|
+
align: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right',
|
|
50
|
+
): { align: 'start' | 'center' | 'end'; side: 'top' | 'right' | 'bottom' | 'left' } => {
|
|
51
|
+
switch (align) {
|
|
52
|
+
case 'top-left': {
|
|
53
|
+
return {
|
|
54
|
+
align: 'start',
|
|
55
|
+
side: 'bottom',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
case 'top-right': {
|
|
59
|
+
return {
|
|
60
|
+
align: 'end',
|
|
61
|
+
side: 'bottom',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
case 'bottom-left': {
|
|
65
|
+
return {
|
|
66
|
+
align: 'start',
|
|
67
|
+
side: 'top',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
case 'bottom-right': {
|
|
71
|
+
return {
|
|
72
|
+
align: 'end',
|
|
73
|
+
side: 'top',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
default: {
|
|
78
|
+
return {
|
|
79
|
+
align: 'start',
|
|
80
|
+
side: 'bottom',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const alignOffset = $derived(anchor.clientWidth / 2);
|
|
87
|
+
const sideOffset = $derived(-anchor.clientHeight / 2);
|
|
88
|
+
const { side, align } = $derived(getAlignment(position));
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<DropdownMenu.Root open={true} onOpenChange={() => onClose()}>
|
|
92
|
+
<DropdownMenu.Portal>
|
|
93
|
+
<DropdownMenu.Content forceMount customAnchor={anchor} {side} {align} {alignOffset} {sideOffset}>
|
|
94
|
+
{#snippet child({ wrapperProps, props, open })}
|
|
95
|
+
{#if open}
|
|
96
|
+
<div {...wrapperProps} class={zIndex.ContextMenu}>
|
|
97
|
+
<div {...props} {...restProps} class={cleanClass(wrapperStyles({ size }), className)} transition:fly>
|
|
98
|
+
{#each items as item, i (isDivider(item) ? i : item.title)}
|
|
99
|
+
{#if isDivider(item)}
|
|
100
|
+
<DropdownMenu.Separator class="my-0.5 border-t" />
|
|
101
|
+
{:else}
|
|
102
|
+
<DropdownMenu.Item
|
|
103
|
+
textValue={item.title}
|
|
104
|
+
closeOnSelect
|
|
105
|
+
onSelect={(event) => item.onSelect?.({ event, item })}
|
|
106
|
+
class="px-1"
|
|
107
|
+
>
|
|
108
|
+
<div class={itemStyles({ color: item.color })}>
|
|
109
|
+
<Icon icon={item.icon} class="m-2 shrink-0" />
|
|
110
|
+
<Text class="grow text-start">{item.title}</Text>
|
|
111
|
+
</div>
|
|
112
|
+
</DropdownMenu.Item>
|
|
113
|
+
{/if}
|
|
114
|
+
{/each}
|
|
115
|
+
|
|
116
|
+
{#if bottomItems}
|
|
117
|
+
<DropdownMenu.Separator class="my-0.5 border-t" />
|
|
118
|
+
<div class="flex gap-1 px-1">
|
|
119
|
+
{#each bottomItems as item (item.title)}
|
|
120
|
+
<DropdownMenu.Item
|
|
121
|
+
textValue={item.title}
|
|
122
|
+
closeOnSelect
|
|
123
|
+
onSelect={(event) => item.onSelect?.({ event, item })}
|
|
124
|
+
title={item.title}
|
|
125
|
+
>
|
|
126
|
+
<div class={cleanClass(itemStyles({ color: item.color }))}>
|
|
127
|
+
<Icon icon={item.icon} class="m-2 shrink-0" />
|
|
128
|
+
</div>
|
|
129
|
+
</DropdownMenu.Item>
|
|
130
|
+
{/each}
|
|
131
|
+
</div>
|
|
132
|
+
{/if}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
{/if}
|
|
136
|
+
{/snippet}
|
|
137
|
+
</DropdownMenu.Content>
|
|
138
|
+
</DropdownMenu.Portal>
|
|
139
|
+
</DropdownMenu.Root>
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
import Icon from '../Icon/Icon.svelte';
|
|
10
10
|
import Logo from '../Logo/Logo.svelte';
|
|
11
11
|
import { ChildKey, zIndex } from '../../constants.js';
|
|
12
|
+
import { commandPaletteManager } from '../../services/command-palette-manager.svelte.js';
|
|
12
13
|
import type { ModalSize } from '../../types.js';
|
|
13
14
|
import { cleanClass } from '../../utilities/internal.js';
|
|
14
15
|
import { Dialog } from 'bits-ui';
|
|
15
|
-
import { tick, type Snippet } from 'svelte';
|
|
16
|
+
import { onMount, tick, type Snippet } from 'svelte';
|
|
16
17
|
import { tv } from 'tailwind-variants';
|
|
17
18
|
|
|
18
19
|
type Props = {
|
|
@@ -85,6 +86,8 @@
|
|
|
85
86
|
|
|
86
87
|
const interactOutsideBehavior = $derived(closeOnBackdropClick ? 'close' : 'ignore');
|
|
87
88
|
const escapeKeydownBehavior = $derived(closeOnEsc ? 'close' : 'ignore');
|
|
89
|
+
|
|
90
|
+
onMount(() => commandPaletteManager.pushContextLayer());
|
|
88
91
|
</script>
|
|
89
92
|
|
|
90
93
|
<Dialog.Root open={true} onOpenChange={(isOpen: boolean) => !isOpen && handleClose()}>
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export { default as Checkbox } from './components/Checkbox/Checkbox.svelte';
|
|
|
27
27
|
export { default as CloseButton } from './components/CloseButton/CloseButton.svelte';
|
|
28
28
|
export { default as Code } from './components/Code/Code.svelte';
|
|
29
29
|
export { default as CodeBlock } from './components/CodeBlock/CodeBlock.svelte';
|
|
30
|
-
export { default as
|
|
30
|
+
export { default as CommandPaletteContext } from './components/CommandPalette/CommandPaletteContext.svelte';
|
|
31
31
|
export { default as ConfirmModal } from './components/ConfirmModal/ConfirmModal.svelte';
|
|
32
32
|
export { default as Container } from './components/Container/Container.svelte';
|
|
33
33
|
export { default as Field } from './components/Field/Field.svelte';
|
|
@@ -68,6 +68,7 @@ export { default as ToastContainer } from './components/Toast/ToastContainer.sve
|
|
|
68
68
|
export { default as ToastContent } from './components/Toast/ToastContent.svelte';
|
|
69
69
|
export { default as ToastPanel } from './components/Toast/ToastPanel.svelte';
|
|
70
70
|
export * from './services/command-palette-manager.svelte.js';
|
|
71
|
+
export * from './services/menu-manager.svelte.js';
|
|
71
72
|
export * from './services/modal-manager.svelte.js';
|
|
72
73
|
export * from './services/theme.svelte.js';
|
|
73
74
|
export * from './services/toast-manager.svelte.js';
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { default as Checkbox } from './components/Checkbox/Checkbox.svelte';
|
|
|
29
29
|
export { default as CloseButton } from './components/CloseButton/CloseButton.svelte';
|
|
30
30
|
export { default as Code } from './components/Code/Code.svelte';
|
|
31
31
|
export { default as CodeBlock } from './components/CodeBlock/CodeBlock.svelte';
|
|
32
|
-
export { default as
|
|
32
|
+
export { default as CommandPaletteContext } from './components/CommandPalette/CommandPaletteContext.svelte';
|
|
33
33
|
export { default as ConfirmModal } from './components/ConfirmModal/ConfirmModal.svelte';
|
|
34
34
|
export { default as Container } from './components/Container/Container.svelte';
|
|
35
35
|
export { default as Field } from './components/Field/Field.svelte';
|
|
@@ -71,6 +71,7 @@ export { default as ToastContent } from './components/Toast/ToastContent.svelte'
|
|
|
71
71
|
export { default as ToastPanel } from './components/Toast/ToastPanel.svelte';
|
|
72
72
|
// helpers
|
|
73
73
|
export * from './services/command-palette-manager.svelte.js';
|
|
74
|
+
export * from './services/menu-manager.svelte.js';
|
|
74
75
|
export * from './services/modal-manager.svelte.js';
|
|
75
76
|
export * from './services/theme.svelte.js';
|
|
76
77
|
export * from './services/toast-manager.svelte.js';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { shortcuts } from '../actions/shortcut.js';
|
|
3
|
+
import CloseButton from '../components/CloseButton/CloseButton.svelte';
|
|
4
|
+
import CommandPaletteItem from '../components/CommandPalette/CommandPaletteItem.svelte';
|
|
5
|
+
import Icon from '../components/Icon/Icon.svelte';
|
|
6
|
+
import Input from '../components/Input/Input.svelte';
|
|
7
|
+
import Modal from '../components/Modal/Modal.svelte';
|
|
8
|
+
import ModalBody from '../components/Modal/ModalBody.svelte';
|
|
9
|
+
import ModalFooter from '../components/Modal/ModalFooter.svelte';
|
|
10
|
+
import ModalHeader from '../components/Modal/ModalHeader.svelte';
|
|
11
|
+
import Stack from '../components/Stack/Stack.svelte';
|
|
12
|
+
import Text from '../components/Text/Text.svelte';
|
|
13
|
+
import {
|
|
14
|
+
commandPaletteManager,
|
|
15
|
+
type CommandPaletteTranslations,
|
|
16
|
+
} from '../services/command-palette-manager.svelte.js';
|
|
17
|
+
import { t } from '../services/translation.svelte.js';
|
|
18
|
+
import { mdiArrowDown, mdiArrowUp, mdiKeyboardEsc, mdiKeyboardReturn, mdiMagnify } from '@mdi/js';
|
|
19
|
+
|
|
20
|
+
type Props = {
|
|
21
|
+
onClose: () => void;
|
|
22
|
+
translations?: CommandPaletteTranslations;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleUp = (event: KeyboardEvent) => handleNavigate(event, 'up');
|
|
26
|
+
const handleDown = (event: KeyboardEvent) => handleNavigate(event, 'down');
|
|
27
|
+
const handleSelect = (event: KeyboardEvent) => handleNavigate(event, 'select');
|
|
28
|
+
const handleNavigate = async (event: KeyboardEvent, direction: 'up' | 'down' | 'select') => {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
|
|
31
|
+
switch (direction) {
|
|
32
|
+
case 'up': {
|
|
33
|
+
commandPaletteManager.up();
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
case 'down': {
|
|
38
|
+
commandPaletteManager.down();
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
case 'select': {
|
|
43
|
+
await commandPaletteManager.select();
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const { onClose, translations }: Props = $props();
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<svelte:window
|
|
53
|
+
use:shortcuts={[
|
|
54
|
+
{ shortcut: { key: 'ArrowUp' }, preventDefault: false, ignoreInputFields: false, onShortcut: handleUp },
|
|
55
|
+
{ shortcut: { key: 'ArrowDown' }, preventDefault: false, ignoreInputFields: false, onShortcut: handleDown },
|
|
56
|
+
{ shortcut: { key: 'k', ctrl: true }, ignoreInputFields: false, onShortcut: handleUp },
|
|
57
|
+
{ shortcut: { key: 'k', meta: true }, ignoreInputFields: false, onShortcut: handleUp },
|
|
58
|
+
{ shortcut: { key: 'j', ctrl: true }, ignoreInputFields: false, onShortcut: handleDown },
|
|
59
|
+
{ shortcut: { key: 'j', meta: true }, ignoreInputFields: false, onShortcut: handleDown },
|
|
60
|
+
{ shortcut: { key: 'Enter' }, ignoreInputFields: false, onShortcut: handleSelect },
|
|
61
|
+
]}
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
<Modal size="large" {onClose} closeOnBackdropClick>
|
|
65
|
+
<ModalHeader>
|
|
66
|
+
<div class="flex place-items-center gap-1">
|
|
67
|
+
<Input
|
|
68
|
+
bind:value={commandPaletteManager.query}
|
|
69
|
+
placeholder={t('search_placeholder', translations)}
|
|
70
|
+
leadingIcon={mdiMagnify}
|
|
71
|
+
tabindex={1}
|
|
72
|
+
/>
|
|
73
|
+
<div>
|
|
74
|
+
<CloseButton onclick={() => commandPaletteManager.close()} class="md:hidden" />
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</ModalHeader>
|
|
78
|
+
<ModalBody>
|
|
79
|
+
<Stack gap={2}>
|
|
80
|
+
{#if commandPaletteManager.query}
|
|
81
|
+
{#if commandPaletteManager.results.length === 0}
|
|
82
|
+
<Text>{t('search_no_results', translations)}</Text>
|
|
83
|
+
{/if}
|
|
84
|
+
{:else if commandPaletteManager.recentItems.length > 0}
|
|
85
|
+
<Text>{t('search_recently_used', translations)}</Text>
|
|
86
|
+
{:else}
|
|
87
|
+
<Text>{t('command_palette_prompt_default', translations)}</Text>
|
|
88
|
+
{/if}
|
|
89
|
+
|
|
90
|
+
{#if commandPaletteManager.results.length > 0}
|
|
91
|
+
<div class="flex flex-col">
|
|
92
|
+
{#each commandPaletteManager.results as item, i (i)}
|
|
93
|
+
<CommandPaletteItem
|
|
94
|
+
{item}
|
|
95
|
+
selected={commandPaletteManager.selectedIndex === i}
|
|
96
|
+
onRemove={commandPaletteManager.query ? undefined : () => commandPaletteManager.remove(i)}
|
|
97
|
+
onSelect={() => commandPaletteManager.select(i)}
|
|
98
|
+
/>
|
|
99
|
+
{/each}
|
|
100
|
+
</div>
|
|
101
|
+
{/if}
|
|
102
|
+
</Stack>
|
|
103
|
+
</ModalBody>
|
|
104
|
+
<ModalFooter>
|
|
105
|
+
<div class="flex w-full justify-around">
|
|
106
|
+
<div class="flex gap-4">
|
|
107
|
+
<div class="flex place-items-center gap-1">
|
|
108
|
+
<span class="rounded bg-gray-300 p-1 dark:bg-gray-500">
|
|
109
|
+
<Icon icon={mdiKeyboardReturn} size="1rem" />
|
|
110
|
+
</span>
|
|
111
|
+
<Text size="small">to select</Text>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="flex place-items-center gap-1">
|
|
115
|
+
<span class="flex gap-1 rounded bg-gray-300 p-1 dark:bg-gray-500">
|
|
116
|
+
<Icon icon={mdiArrowUp} size="1rem" />
|
|
117
|
+
<Icon icon={mdiArrowDown} size="1rem" />
|
|
118
|
+
</span>
|
|
119
|
+
<Text size="small">to navigate</Text>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div class="flex place-items-center gap-1">
|
|
123
|
+
<span class="rounded bg-gray-300 p-1 dark:bg-gray-500">
|
|
124
|
+
<Icon icon={mdiKeyboardEsc} size="1rem" />
|
|
125
|
+
</span>
|
|
126
|
+
<Text size="small">to close</Text>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</ModalFooter>
|
|
131
|
+
</Modal>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CommandPaletteTranslations } from '../services/command-palette-manager.svelte.js';
|
|
2
|
+
type Props = {
|
|
3
|
+
onClose: () => void;
|
|
4
|
+
translations?: CommandPaletteTranslations;
|
|
5
|
+
};
|
|
6
|
+
declare const CommandPaletteModal: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type CommandPaletteModal = ReturnType<typeof CommandPaletteModal>;
|
|
8
|
+
export default CommandPaletteModal;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import IconButton from '../components/IconButton/IconButton.svelte';
|
|
6
6
|
import Input from '../components/Input/Input.svelte';
|
|
7
7
|
import Label from '../components/Label/Label.svelte';
|
|
8
|
+
import { zIndex } from '../constants.js';
|
|
8
9
|
import type { SelectCommonProps, SelectItem } from '../types.js';
|
|
9
10
|
import { cleanClass, generateId } from '../utilities/internal.js';
|
|
10
11
|
import { mdiArrowDown, mdiArrowUp, mdiCheck, mdiUnfoldMoreHorizontal } from '@mdi/js';
|
|
@@ -115,7 +116,7 @@
|
|
|
115
116
|
<Select.Portal>
|
|
116
117
|
<Select.Content
|
|
117
118
|
bind:ref={contentRef}
|
|
118
|
-
class="bg-light text-dark max-h-96 rounded-xl border py-3 outline-none select-none"
|
|
119
|
+
class="bg-light text-dark max-h-96 rounded-xl border py-3 outline-none select-none {zIndex.SelectDropdown}"
|
|
119
120
|
sideOffset={10}
|
|
120
121
|
>
|
|
121
122
|
<Select.ScrollUpButton class="flex w-full items-center justify-center">
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type Shortcut } from '../actions/shortcut.js';
|
|
2
|
+
import type { MaybeArray, TranslationProps } from '../types.js';
|
|
1
3
|
export type CommandItem = {
|
|
2
4
|
icon: string;
|
|
3
5
|
iconClass: string;
|
|
@@ -5,32 +7,54 @@ export type CommandItem = {
|
|
|
5
7
|
title: string;
|
|
6
8
|
description?: string;
|
|
7
9
|
text: string;
|
|
10
|
+
shortcuts?: MaybeArray<Shortcut>;
|
|
11
|
+
shortcutOptions?: {
|
|
12
|
+
ignoreInputFields?: boolean;
|
|
13
|
+
preventDefault?: boolean;
|
|
14
|
+
};
|
|
8
15
|
} & ({
|
|
9
16
|
href: string;
|
|
10
17
|
} | {
|
|
11
|
-
action: () => void
|
|
18
|
+
action: () => void | Promise<void>;
|
|
12
19
|
});
|
|
20
|
+
export type CommandPaletteTranslations = TranslationProps<'search_placeholder' | 'search_no_results' | 'search_recently_used' | 'command_palette_prompt_default'>;
|
|
13
21
|
export declare const asText: (...items: unknown[]) => string;
|
|
14
22
|
declare class CommandPaletteManager {
|
|
15
|
-
|
|
16
|
-
isOpen: boolean;
|
|
23
|
+
#private;
|
|
17
24
|
query: string;
|
|
18
25
|
selectedIndex: number;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
items: (CommandItem & {
|
|
27
|
+
id: string;
|
|
28
|
+
})[];
|
|
29
|
+
filteredItems: (CommandItem & {
|
|
30
|
+
id: string;
|
|
31
|
+
})[];
|
|
32
|
+
recentItems: (CommandItem & {
|
|
33
|
+
id: string;
|
|
34
|
+
})[];
|
|
35
|
+
results: (CommandItem & {
|
|
36
|
+
id: string;
|
|
37
|
+
})[];
|
|
38
|
+
get isEnabled(): boolean;
|
|
24
39
|
enable(): void;
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
setTranslations(translations?: CommandPaletteTranslations): void;
|
|
41
|
+
pushContextLayer(): (() => void) | undefined;
|
|
42
|
+
popContextLayer(): void;
|
|
43
|
+
open(): void;
|
|
44
|
+
close(): Promise<void> | undefined;
|
|
27
45
|
select(selectedIndex?: number): Promise<void>;
|
|
28
46
|
remove(index: number): void;
|
|
29
47
|
up(): void;
|
|
30
48
|
down(): void;
|
|
31
49
|
reset(): void;
|
|
32
|
-
addCommands(itemOrItems: CommandItem
|
|
33
|
-
|
|
50
|
+
addCommands(itemOrItems: MaybeArray<CommandItem & {
|
|
51
|
+
id?: string;
|
|
52
|
+
}>, options?: {
|
|
53
|
+
global?: boolean;
|
|
54
|
+
}): () => void;
|
|
55
|
+
removeCommands(itemOrItems: MaybeArray<{
|
|
56
|
+
id: string;
|
|
57
|
+
}>): void;
|
|
34
58
|
}
|
|
35
59
|
export declare const commandPaletteManager: CommandPaletteManager;
|
|
36
60
|
export {};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { goto } from '$app/navigation';
|
|
2
|
+
import { matchesShortcut, shortcuts, shouldIgnoreEvent } from '../actions/shortcut.js';
|
|
3
|
+
import CommandPaletteModal from '../internal/CommandPaletteModal.svelte';
|
|
4
|
+
import { generateId } from '../utilities/internal.js';
|
|
5
|
+
import { modalManager } from './modal-manager.svelte.js';
|
|
2
6
|
export const asText = (...items) => {
|
|
3
7
|
return items
|
|
4
8
|
.filter((item) => item !== undefined && item !== null)
|
|
@@ -6,9 +10,6 @@ export const asText = (...items) => {
|
|
|
6
10
|
.join('|')
|
|
7
11
|
.toLowerCase();
|
|
8
12
|
};
|
|
9
|
-
const isEqual = (a, b) => {
|
|
10
|
-
return a.title === b.title && a.type === b.type;
|
|
11
|
-
};
|
|
12
13
|
const isMatch = (item, query) => {
|
|
13
14
|
if (!query) {
|
|
14
15
|
return true;
|
|
@@ -16,31 +17,110 @@ const isMatch = (item, query) => {
|
|
|
16
17
|
return item.text.includes(query);
|
|
17
18
|
};
|
|
18
19
|
class CommandPaletteManager {
|
|
19
|
-
isEnabled = $state(false);
|
|
20
|
-
isOpen = $state(false);
|
|
21
20
|
query = $state('');
|
|
22
21
|
selectedIndex = $state(0);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
#isEnabled = $state(false);
|
|
23
|
+
#normalizedQuery = $derived(this.query.toLowerCase());
|
|
24
|
+
#modal;
|
|
25
|
+
#translations = {};
|
|
26
|
+
#isOpen = false;
|
|
27
|
+
#globalLayer = $state({ items: [], recentItems: [] });
|
|
28
|
+
#layers = $state([{ items: [], recentItems: [] }]);
|
|
29
|
+
items = $derived([...this.#globalLayer.items, ...this.#layers.at(-1).items]);
|
|
30
|
+
filteredItems = $derived(this.items.filter((item) => isMatch(item, this.#normalizedQuery)).slice(0, 100));
|
|
31
|
+
recentItems = $derived([...this.#globalLayer.recentItems, ...this.#layers.at(-1).recentItems]);
|
|
27
32
|
results = $derived(this.query ? this.filteredItems : this.recentItems);
|
|
33
|
+
get isEnabled() {
|
|
34
|
+
return this.#isEnabled;
|
|
35
|
+
}
|
|
28
36
|
enable() {
|
|
29
|
-
this
|
|
37
|
+
if (this.#isEnabled) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
this.#isEnabled = true;
|
|
41
|
+
if (globalThis.window && document.body) {
|
|
42
|
+
shortcuts(document.body, [
|
|
43
|
+
{ shortcut: { key: 'k', meta: true }, onShortcut: () => this.open() },
|
|
44
|
+
{ shortcut: { key: 'k', ctrl: true }, onShortcut: () => this.open() },
|
|
45
|
+
{ shortcut: { key: '/' }, preventDefault: true, onShortcut: () => this.open() },
|
|
46
|
+
]);
|
|
47
|
+
document.body.addEventListener('keydown', (event) => this.#handleKeydown(event));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async #handleKeydown(event) {
|
|
51
|
+
const command = this.items.find(({ shortcuts }) => {
|
|
52
|
+
if (!shortcuts) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (shortcuts)
|
|
56
|
+
return Array.isArray(shortcuts)
|
|
57
|
+
? shortcuts.some((shortcut) => matchesShortcut(event, shortcut))
|
|
58
|
+
: matchesShortcut(event, shortcuts);
|
|
59
|
+
});
|
|
60
|
+
if (!command) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const { ignoreInputFields = true, preventDefault = true } = command.shortcutOptions ?? {};
|
|
64
|
+
if (ignoreInputFields && shouldIgnoreEvent(event)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (preventDefault) {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
}
|
|
70
|
+
await this.#executeCommand(command);
|
|
71
|
+
}
|
|
72
|
+
async #executeCommand(command) {
|
|
73
|
+
if ('href' in command) {
|
|
74
|
+
if (!command.href.startsWith('/')) {
|
|
75
|
+
window.open(command.href, '_blank');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
await goto(command.href);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
await command.action();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
setTranslations(translations = {}) {
|
|
86
|
+
this.#translations = translations;
|
|
30
87
|
}
|
|
31
|
-
|
|
32
|
-
if (!this
|
|
88
|
+
pushContextLayer() {
|
|
89
|
+
if (!this.#isEnabled) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// we do not want the command palette to have its own context layer
|
|
93
|
+
if (this.#isOpen) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.#layers.push({ items: [], recentItems: [] });
|
|
97
|
+
return () => this.popContextLayer();
|
|
98
|
+
}
|
|
99
|
+
popContextLayer() {
|
|
100
|
+
if (this.#layers.length > 1) {
|
|
101
|
+
this.#layers = this.#layers.slice(0, -1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
open() {
|
|
105
|
+
if (this.#modal || !this.#isEnabled) {
|
|
33
106
|
return;
|
|
34
107
|
}
|
|
35
108
|
this.selectedIndex = 0;
|
|
36
|
-
this
|
|
109
|
+
this.#isOpen = true;
|
|
110
|
+
const { close, onClose } = modalManager.open(CommandPaletteModal, { translations: this.#translations });
|
|
111
|
+
this.#modal = { close };
|
|
112
|
+
void onClose.then(() => this.#onClose());
|
|
37
113
|
}
|
|
38
114
|
close() {
|
|
39
|
-
if (!this
|
|
115
|
+
if (!this.#modal) {
|
|
40
116
|
return;
|
|
41
117
|
}
|
|
118
|
+
return this.#modal.close();
|
|
119
|
+
}
|
|
120
|
+
#onClose() {
|
|
42
121
|
this.query = '';
|
|
43
|
-
this
|
|
122
|
+
this.#modal = undefined;
|
|
123
|
+
this.#isOpen = false;
|
|
44
124
|
}
|
|
45
125
|
async select(selectedIndex) {
|
|
46
126
|
const selected = this.results[selectedIndex ?? this.selectedIndex];
|
|
@@ -48,45 +128,48 @@ class CommandPaletteManager {
|
|
|
48
128
|
return;
|
|
49
129
|
}
|
|
50
130
|
// no duplicates
|
|
51
|
-
this.recentItems = this.recentItems.filter((
|
|
131
|
+
this.recentItems = this.recentItems.filter(({ id }) => id !== selected.id);
|
|
52
132
|
this.recentItems.unshift(selected);
|
|
53
133
|
this.recentItems = this.recentItems.slice(0, 5);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
window.open(selected.href, '_blank');
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
await goto(selected.href);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
await selected.action();
|
|
64
|
-
}
|
|
65
|
-
this.close();
|
|
134
|
+
await this.#executeCommand(selected);
|
|
135
|
+
await this.close();
|
|
66
136
|
}
|
|
67
137
|
remove(index) {
|
|
68
138
|
this.recentItems.splice(index, 1);
|
|
69
139
|
}
|
|
70
140
|
up() {
|
|
71
|
-
this.selectedIndex = (this.selectedIndex - 1 + this.results.length) % this.results.length;
|
|
141
|
+
this.selectedIndex = (this.selectedIndex - 1 + this.results.length) % (this.results.length || 1);
|
|
72
142
|
}
|
|
73
143
|
down() {
|
|
74
|
-
this.selectedIndex = (this.selectedIndex + 1) % this.results.length;
|
|
144
|
+
this.selectedIndex = (this.selectedIndex + 1) % (this.results.length || 1);
|
|
75
145
|
}
|
|
76
146
|
reset() {
|
|
77
|
-
this
|
|
78
|
-
this
|
|
147
|
+
this.#layers = [{ items: [], recentItems: [] }];
|
|
148
|
+
this.#globalLayer = { items: [], recentItems: [] };
|
|
79
149
|
this.query = '';
|
|
80
150
|
}
|
|
81
|
-
addCommands(itemOrItems) {
|
|
151
|
+
addCommands(itemOrItems, options = {}) {
|
|
82
152
|
const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
|
|
83
|
-
|
|
153
|
+
const itemsWithId = items.map((item) => ({ ...item, id: item.id ?? generateId() }));
|
|
154
|
+
if (options.global) {
|
|
155
|
+
this.#globalLayer.items.push(...itemsWithId);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
this.#layers.at(-1).items.push(...itemsWithId);
|
|
159
|
+
}
|
|
160
|
+
return () => this.removeCommands(itemsWithId);
|
|
161
|
+
}
|
|
162
|
+
#removeCommands(layer, ids) {
|
|
163
|
+
return {
|
|
164
|
+
items: layer.items.filter(({ id }) => !ids[id]),
|
|
165
|
+
recentItems: layer.recentItems.filter(({ id }) => !ids[id]),
|
|
166
|
+
};
|
|
84
167
|
}
|
|
85
168
|
removeCommands(itemOrItems) {
|
|
86
169
|
const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
170
|
+
const ids = items.reduce((acc, { id }) => ({ ...acc, [id]: true }), {});
|
|
171
|
+
this.#layers = this.#layers.map((layer) => this.#removeCommands(layer, ids));
|
|
172
|
+
this.#globalLayer = this.#removeCommands(this.#globalLayer, ids);
|
|
90
173
|
}
|
|
91
174
|
}
|
|
92
175
|
export const commandPaletteManager = new CommandPaletteManager();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import ContextMenu from '../components/ContextMenu/ContextMenu.svelte';
|
|
2
|
+
import { modalManager } from './modal-manager.svelte.js';
|
|
3
|
+
class MenuManager {
|
|
4
|
+
show(event, props) {
|
|
5
|
+
return modalManager.show(ContextMenu, {
|
|
6
|
+
...props,
|
|
7
|
+
anchor: event.currentTarget,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export const menuManager = new MenuManager();
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { type Component, type ComponentProps } from 'svelte';
|
|
2
2
|
import ConfirmModal from '../components/ConfirmModal/ConfirmModal.svelte';
|
|
3
3
|
type OnCloseData<T> = T extends {
|
|
4
|
-
onClose: (data?: infer R) => void;
|
|
5
|
-
} ? R | undefined : T extends {
|
|
6
4
|
onClose: (data: infer R) => void;
|
|
7
|
-
} ? R :
|
|
5
|
+
} ? unknown extends R ? void : R : T extends {
|
|
6
|
+
onClose: (data?: infer R) => void;
|
|
7
|
+
} ? R | undefined : never;
|
|
8
8
|
type ExtendsEmptyObject<T> = keyof T extends never ? never : T;
|
|
9
|
-
type
|
|
9
|
+
type StripParamIfOptional<T> = T extends void ? [] : [T];
|
|
10
10
|
type OptionalParamIfEmpty<T> = ExtendsEmptyObject<T> extends never ? [] | [Record<string, never> | undefined] : [T];
|
|
11
11
|
declare class ModalManager {
|
|
12
12
|
#private;
|
|
13
13
|
get openCount(): number;
|
|
14
|
-
show<T extends object>(Component: Component<T>, ...props: OptionalParamIfEmpty<Omit<T, 'onClose'>>): Promise<
|
|
14
|
+
show<T extends object>(Component: Component<T>, ...props: OptionalParamIfEmpty<Omit<T, 'onClose'>>): Promise<OnCloseData<T>>;
|
|
15
15
|
open<T extends object, K = OnCloseData<T>>(Component: Component<T>, ...props: OptionalParamIfEmpty<Omit<T, 'onClose'>>): {
|
|
16
|
-
onClose: Promise<
|
|
17
|
-
close: (
|
|
16
|
+
onClose: Promise<K>;
|
|
17
|
+
close: (...args: StripParamIfOptional<K>) => Promise<void>;
|
|
18
18
|
};
|
|
19
19
|
showDialog(options: Omit<ComponentProps<typeof ConfirmModal>, 'onClose'>): Promise<boolean>;
|
|
20
20
|
}
|
|
@@ -16,7 +16,7 @@ class ModalManager {
|
|
|
16
16
|
await unmount(modal);
|
|
17
17
|
this.#openCount--;
|
|
18
18
|
// make sure bits-ui clean up finishes before resolving
|
|
19
|
-
setTimeout(() => resolve(args
|
|
19
|
+
setTimeout(() => resolve(args[0]), 10);
|
|
20
20
|
};
|
|
21
21
|
modal = mount(Component, {
|
|
22
22
|
target: document.body,
|
|
@@ -29,7 +29,7 @@ class ModalManager {
|
|
|
29
29
|
});
|
|
30
30
|
return {
|
|
31
31
|
onClose: deferred,
|
|
32
|
-
close: (...args) => onClose(args
|
|
32
|
+
close: (...args) => onClose(...args),
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
showDialog(options) {
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type HeadingColor = TextColor;
|
|
|
9
9
|
export type Size = 'tiny' | 'small' | 'medium' | 'large' | 'giant';
|
|
10
10
|
export type ModalSize = Size | 'full';
|
|
11
11
|
export type ContainerSize = ModalSize;
|
|
12
|
+
export type MenuSize = ModalSize;
|
|
12
13
|
export type HeadingSize = Size | 'title';
|
|
13
14
|
export type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p';
|
|
14
15
|
export type Shape = 'rectangle' | 'semi-round' | 'round';
|
|
@@ -25,6 +26,7 @@ export type TranslationProps<T extends keyof Translations> = {
|
|
|
25
26
|
export type IconLike = string | {
|
|
26
27
|
path: string;
|
|
27
28
|
};
|
|
29
|
+
export type MaybeArray<T> = T | T[];
|
|
28
30
|
export type IconProps = {
|
|
29
31
|
icon: IconLike;
|
|
30
32
|
title?: string;
|
|
@@ -202,4 +204,31 @@ export type ToastButton = {
|
|
|
202
204
|
variant?: Variants;
|
|
203
205
|
onClick: () => void;
|
|
204
206
|
};
|
|
207
|
+
export type MenuSelectHandler = (context: {
|
|
208
|
+
event: Event;
|
|
209
|
+
item: MenuItem;
|
|
210
|
+
}) => void;
|
|
211
|
+
export type MenuItem = {
|
|
212
|
+
title: string;
|
|
213
|
+
icon: IconLike;
|
|
214
|
+
color?: Color;
|
|
215
|
+
onSelect?: MenuSelectHandler;
|
|
216
|
+
};
|
|
217
|
+
export declare enum MenuItemType {
|
|
218
|
+
Divider = "divider"
|
|
219
|
+
}
|
|
220
|
+
export type MenuItems = Array<MenuItem | MenuItemType>;
|
|
221
|
+
export type MenuProps = {
|
|
222
|
+
items: MenuItems;
|
|
223
|
+
bottomItems?: MenuItem[];
|
|
224
|
+
size?: MenuSize;
|
|
225
|
+
} & HTMLAttributes<HTMLDivElement>;
|
|
226
|
+
export type ContextMenuPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
227
|
+
export type ContextMenuBaseProps = MenuProps & {
|
|
228
|
+
position?: ContextMenuPosition;
|
|
229
|
+
};
|
|
230
|
+
export type ContextMenuProps = ContextMenuBaseProps & {
|
|
231
|
+
onClose: () => void;
|
|
232
|
+
anchor: HTMLElement;
|
|
233
|
+
};
|
|
205
234
|
export {};
|
package/dist/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { shortcuts } from '../../actions/shortcut.js';
|
|
3
|
-
import CloseButton from '../CloseButton/CloseButton.svelte';
|
|
4
|
-
import CommandPaletteItem from './CommandPaletteItem.svelte';
|
|
5
|
-
import Icon from '../Icon/Icon.svelte';
|
|
6
|
-
import Input from '../Input/Input.svelte';
|
|
7
|
-
import Modal from '../Modal/Modal.svelte';
|
|
8
|
-
import ModalBody from '../Modal/ModalBody.svelte';
|
|
9
|
-
import ModalFooter from '../Modal/ModalFooter.svelte';
|
|
10
|
-
import ModalHeader from '../Modal/ModalHeader.svelte';
|
|
11
|
-
import Stack from '../Stack/Stack.svelte';
|
|
12
|
-
import Text from '../Text/Text.svelte';
|
|
13
|
-
import { commandPaletteManager } from '../../services/command-palette-manager.svelte';
|
|
14
|
-
import { t } from '../../services/translation.svelte.js';
|
|
15
|
-
import type { TranslationProps } from '../../types.js';
|
|
16
|
-
import { mdiArrowDown, mdiArrowUp, mdiKeyboardEsc, mdiKeyboardReturn, mdiMagnify } from '@mdi/js';
|
|
17
|
-
|
|
18
|
-
type Props = {
|
|
19
|
-
translations?: TranslationProps<
|
|
20
|
-
'search_placeholder' | 'search_no_results' | 'search_recently_used' | 'command_palette_prompt_default'
|
|
21
|
-
>;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
let { translations }: Props = $props();
|
|
25
|
-
|
|
26
|
-
let inputElement = $state<HTMLInputElement | null>(null);
|
|
27
|
-
|
|
28
|
-
const handleOpen = () => commandPaletteManager.open();
|
|
29
|
-
const handleClose = () => commandPaletteManager.close();
|
|
30
|
-
const handleUp = (event: KeyboardEvent) => handleNavigate(event, 'up');
|
|
31
|
-
const handleDown = (event: KeyboardEvent) => handleNavigate(event, 'down');
|
|
32
|
-
const handleSelect = (event: KeyboardEvent) => handleNavigate(event, 'select');
|
|
33
|
-
const handleNavigate = async (event: KeyboardEvent, direction: 'up' | 'down' | 'select') => {
|
|
34
|
-
if (!commandPaletteManager.isOpen) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
event.preventDefault();
|
|
39
|
-
|
|
40
|
-
switch (direction) {
|
|
41
|
-
case 'up': {
|
|
42
|
-
commandPaletteManager.up();
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
case 'down': {
|
|
47
|
-
commandPaletteManager.down();
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
case 'select': {
|
|
52
|
-
await commandPaletteManager.select();
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
</script>
|
|
58
|
-
|
|
59
|
-
<svelte:window
|
|
60
|
-
use:shortcuts={[
|
|
61
|
-
{ shortcut: { key: 'k', meta: true }, onShortcut: handleOpen },
|
|
62
|
-
{ shortcut: { key: 'k', ctrl: true }, onShortcut: handleOpen },
|
|
63
|
-
{ shortcut: { key: '/' }, preventDefault: true, onShortcut: handleOpen },
|
|
64
|
-
{ shortcut: { key: 'ArrowUp' }, preventDefault: false, ignoreInputFields: false, onShortcut: handleUp },
|
|
65
|
-
{ shortcut: { key: 'ArrowDown' }, preventDefault: false, ignoreInputFields: false, onShortcut: handleDown },
|
|
66
|
-
{ shortcut: { key: 'k', ctrl: true }, ignoreInputFields: false, onShortcut: handleUp },
|
|
67
|
-
{ shortcut: { key: 'k', meta: true }, ignoreInputFields: false, onShortcut: handleUp },
|
|
68
|
-
{ shortcut: { key: 'j', ctrl: true }, ignoreInputFields: false, onShortcut: handleDown },
|
|
69
|
-
{ shortcut: { key: 'j', meta: true }, ignoreInputFields: false, onShortcut: handleDown },
|
|
70
|
-
{ shortcut: { key: 'Enter' }, ignoreInputFields: false, onShortcut: handleSelect },
|
|
71
|
-
{ shortcut: { key: 'Escape' }, onShortcut: handleClose },
|
|
72
|
-
]}
|
|
73
|
-
/>
|
|
74
|
-
|
|
75
|
-
{#if commandPaletteManager.isOpen}
|
|
76
|
-
<Modal size="large" onClose={handleClose} closeOnBackdropClick>
|
|
77
|
-
<ModalHeader>
|
|
78
|
-
<div class="flex place-items-center gap-1">
|
|
79
|
-
<Input
|
|
80
|
-
bind:ref={inputElement}
|
|
81
|
-
bind:value={commandPaletteManager.query}
|
|
82
|
-
placeholder={t('search_placeholder', translations)}
|
|
83
|
-
leadingIcon={mdiMagnify}
|
|
84
|
-
tabindex={1}
|
|
85
|
-
/>
|
|
86
|
-
<div>
|
|
87
|
-
<CloseButton onclick={() => commandPaletteManager.close()} class="md:hidden" />
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
</ModalHeader>
|
|
91
|
-
<ModalBody>
|
|
92
|
-
<Stack gap={2}>
|
|
93
|
-
{#if commandPaletteManager.query}
|
|
94
|
-
{#if commandPaletteManager.results.length === 0}
|
|
95
|
-
<Text>{t('search_no_results', translations)}</Text>
|
|
96
|
-
{/if}
|
|
97
|
-
{:else if commandPaletteManager.recentItems.length > 0}
|
|
98
|
-
<Text>{t('search_recently_used', translations)}</Text>
|
|
99
|
-
{:else}
|
|
100
|
-
<Text>{t('command_palette_prompt_default', translations)}</Text>
|
|
101
|
-
{/if}
|
|
102
|
-
|
|
103
|
-
{#if commandPaletteManager.results.length > 0}
|
|
104
|
-
<div class="flex flex-col">
|
|
105
|
-
{#each commandPaletteManager.results as item, i (i)}
|
|
106
|
-
<CommandPaletteItem
|
|
107
|
-
{item}
|
|
108
|
-
selected={commandPaletteManager.selectedIndex === i}
|
|
109
|
-
onRemove={commandPaletteManager.query ? undefined : () => commandPaletteManager.remove(i)}
|
|
110
|
-
onSelect={() => commandPaletteManager.select(i)}
|
|
111
|
-
/>
|
|
112
|
-
{/each}
|
|
113
|
-
</div>
|
|
114
|
-
{/if}
|
|
115
|
-
</Stack>
|
|
116
|
-
</ModalBody>
|
|
117
|
-
<ModalFooter>
|
|
118
|
-
<div class="flex w-full justify-around">
|
|
119
|
-
<div class="flex gap-4">
|
|
120
|
-
<div class="flex place-items-center gap-1">
|
|
121
|
-
<span class="rounded bg-gray-300 p-1 dark:bg-gray-500">
|
|
122
|
-
<Icon icon={mdiKeyboardReturn} size="1rem" />
|
|
123
|
-
</span>
|
|
124
|
-
<Text size="small">to select</Text>
|
|
125
|
-
</div>
|
|
126
|
-
|
|
127
|
-
<div class="flex place-items-center gap-1">
|
|
128
|
-
<span class="flex gap-1 rounded bg-gray-300 p-1 dark:bg-gray-500">
|
|
129
|
-
<Icon icon={mdiArrowUp} size="1rem" />
|
|
130
|
-
<Icon icon={mdiArrowDown} size="1rem" />
|
|
131
|
-
</span>
|
|
132
|
-
<Text size="small">to navigate</Text>
|
|
133
|
-
</div>
|
|
134
|
-
|
|
135
|
-
<div class="flex place-items-center gap-1">
|
|
136
|
-
<span class="rounded bg-gray-300 p-1 dark:bg-gray-500">
|
|
137
|
-
<Icon icon={mdiKeyboardEsc} size="1rem" />
|
|
138
|
-
</span>
|
|
139
|
-
<Text size="small">to close</Text>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
</ModalFooter>
|
|
144
|
-
</Modal>
|
|
145
|
-
{/if}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { TranslationProps } from '../../types.js';
|
|
2
|
-
type Props = {
|
|
3
|
-
translations?: TranslationProps<'search_placeholder' | 'search_no_results' | 'search_recently_used' | 'command_palette_prompt_default'>;
|
|
4
|
-
};
|
|
5
|
-
declare const CommandPalette: import("svelte").Component<Props, {}, "">;
|
|
6
|
-
type CommandPalette = ReturnType<typeof CommandPalette>;
|
|
7
|
-
export default CommandPalette;
|