@immich/ui 0.64.1 → 0.65.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/BasicModal/BasicModal.svelte +5 -1
- package/dist/components/BasicModal/BasicModal.svelte.d.ts +2 -0
- package/dist/components/CommandPalette/CommandPaletteDefaultProvider.svelte +1 -1
- package/dist/components/CommandPalette/CommandPaletteDefaultProvider.svelte.d.ts +1 -1
- package/dist/components/CommandPalette/CommandPaletteItem.svelte +1 -2
- package/dist/components/Modal/Modal.svelte +11 -1
- package/dist/components/Modal/Modal.svelte.d.ts +1 -0
- package/dist/internal/CommandPaletteModal.svelte +28 -19
- package/dist/services/command-palette-manager.svelte.d.ts +16 -5
- package/dist/services/command-palette-manager.svelte.js +53 -10
- package/dist/site/constants.d.ts +0 -1
- package/dist/site/constants.js +0 -1
- package/package.json +2 -2
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
icon?: string | boolean;
|
|
13
13
|
closeText?: string;
|
|
14
14
|
closeColor?: Color;
|
|
15
|
+
closeOnBackdropClick?: boolean;
|
|
16
|
+
closeOnEsc?: boolean;
|
|
15
17
|
size?: ModalSize;
|
|
16
18
|
onClose: () => void;
|
|
17
19
|
children: Snippet;
|
|
@@ -22,13 +24,15 @@
|
|
|
22
24
|
icon,
|
|
23
25
|
closeText = t('close'),
|
|
24
26
|
closeColor = 'secondary',
|
|
27
|
+
closeOnBackdropClick = true,
|
|
28
|
+
closeOnEsc = true,
|
|
25
29
|
size = 'small',
|
|
26
30
|
onClose = () => {},
|
|
27
31
|
children,
|
|
28
32
|
}: Props = $props();
|
|
29
33
|
</script>
|
|
30
34
|
|
|
31
|
-
<Modal {title} {onClose} {size} {icon}>
|
|
35
|
+
<Modal {title} {onClose} {size} {icon} {closeOnBackdropClick} {closeOnEsc}>
|
|
32
36
|
<ModalBody {children} />
|
|
33
37
|
<ModalFooter>
|
|
34
38
|
<Button shape="round" color={closeColor} fullWidth onclick={onClose}>
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import Kbd from '../Kbd/Kbd.svelte';
|
|
7
7
|
import Text from '../Text/Text.svelte';
|
|
8
8
|
import type { ActionItem } from '../../types.js';
|
|
9
|
-
import { cleanClass } from '../../utilities/internal.js';
|
|
10
9
|
|
|
11
10
|
type Props = {
|
|
12
11
|
item: ActionItem;
|
|
@@ -63,7 +62,7 @@
|
|
|
63
62
|
<div class="flex shrink-0 flex-col justify-end gap-1">
|
|
64
63
|
{#each renderedShortcuts as shortcut (shortcut.join('-'))}
|
|
65
64
|
<div class="flex justify-end">
|
|
66
|
-
<Kbd size="tiny"
|
|
65
|
+
<Kbd size="tiny">{shortcut.join(' ')}</Kbd>
|
|
67
66
|
</div>
|
|
68
67
|
{/each}
|
|
69
68
|
</div>
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
expandable?: boolean;
|
|
26
26
|
closeOnEsc?: boolean;
|
|
27
27
|
closeOnBackdropClick?: boolean;
|
|
28
|
+
focusOnOpen?: boolean;
|
|
28
29
|
children: Snippet;
|
|
29
30
|
onClose?: () => void;
|
|
30
31
|
onEscapeKeydown?: (event: KeyboardEvent) => void;
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
class: className,
|
|
41
42
|
closeOnEsc = true,
|
|
42
43
|
closeOnBackdropClick = false,
|
|
44
|
+
focusOnOpen = false,
|
|
43
45
|
children,
|
|
44
46
|
onOpenAutoFocus,
|
|
45
47
|
}: Props = $props();
|
|
@@ -96,6 +98,14 @@
|
|
|
96
98
|
onEscapeKeydown?.(event);
|
|
97
99
|
};
|
|
98
100
|
|
|
101
|
+
const handleOpenAutoFocus = (event: Event) => {
|
|
102
|
+
if (onOpenAutoFocus) {
|
|
103
|
+
onOpenAutoFocus(event);
|
|
104
|
+
} else if (!focusOnOpen) {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
99
109
|
onMount(() => {
|
|
100
110
|
layer = modalState.incrementLayer();
|
|
101
111
|
|
|
@@ -107,7 +117,7 @@
|
|
|
107
117
|
<Dialog.Portal>
|
|
108
118
|
<Dialog.Overlay class="{zIndex.ModalBackdrop} fixed start-0 top-0 flex h-dvh max-h-dvh w-screen bg-black/30" />
|
|
109
119
|
<Dialog.Content
|
|
110
|
-
{
|
|
120
|
+
onOpenAutoFocus={handleOpenAutoFocus}
|
|
111
121
|
onEscapeKeydown={handleEscapeKeydown}
|
|
112
122
|
{escapeKeydownBehavior}
|
|
113
123
|
{interactOutsideBehavior}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { shortcuts } from '../actions/shortcut.js';
|
|
3
3
|
import CloseButton from '../components/CloseButton/CloseButton.svelte';
|
|
4
4
|
import CommandPaletteItem from '../components/CommandPalette/CommandPaletteItem.svelte';
|
|
5
|
+
import Heading from '../components/Heading/Heading.svelte';
|
|
5
6
|
import Icon from '../components/Icon/Icon.svelte';
|
|
6
7
|
import Input from '../components/Input/Input.svelte';
|
|
7
8
|
import Modal from '../components/Modal/Modal.svelte';
|
|
@@ -26,6 +27,10 @@
|
|
|
26
27
|
|
|
27
28
|
const { onClose, translations, initialQuery = '' }: Props = $props();
|
|
28
29
|
|
|
30
|
+
let query = $state(initialQuery);
|
|
31
|
+
|
|
32
|
+
$effect(() => commandPaletteManager.queryUpdate(query));
|
|
33
|
+
|
|
29
34
|
const handleUp = (event: KeyboardEvent) => handleNavigate(event, 'up');
|
|
30
35
|
const handleDown = (event: KeyboardEvent) => handleNavigate(event, 'down');
|
|
31
36
|
const handleSelect = (event: KeyboardEvent) => handleNavigate(event, 'select');
|
|
@@ -34,31 +39,26 @@
|
|
|
34
39
|
|
|
35
40
|
switch (direction) {
|
|
36
41
|
case 'up': {
|
|
37
|
-
|
|
42
|
+
commandPaletteManager.navigateUp();
|
|
38
43
|
break;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
case 'down': {
|
|
42
|
-
if (!query && commandPaletteManager.
|
|
47
|
+
if (!query && commandPaletteManager.results.length === 0) {
|
|
43
48
|
commandPaletteManager.loadAllItems();
|
|
44
49
|
break;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
commandPaletteManager.navigateDown();
|
|
48
53
|
break;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
case 'select': {
|
|
52
|
-
onClose(commandPaletteManager.
|
|
57
|
+
onClose(commandPaletteManager.selectedItem);
|
|
53
58
|
break;
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
61
|
};
|
|
57
|
-
|
|
58
|
-
let selectedIndex = $state(0);
|
|
59
|
-
let query = $state(initialQuery);
|
|
60
|
-
|
|
61
|
-
$effect(() => commandPaletteManager.queryUpdate(query));
|
|
62
62
|
</script>
|
|
63
63
|
|
|
64
64
|
<svelte:window
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
]}
|
|
74
74
|
/>
|
|
75
75
|
|
|
76
|
-
<Modal size="large" {onClose} closeOnBackdropClick class="max-h-[85vh] lg:max-h-[75vh]">
|
|
76
|
+
<Modal size="large" {onClose} closeOnBackdropClick focusOnOpen class="max-h-[85vh] lg:max-h-[75vh]">
|
|
77
77
|
<ModalHeader>
|
|
78
78
|
<div class="flex place-items-center gap-1">
|
|
79
79
|
<Input
|
|
@@ -90,25 +90,34 @@
|
|
|
90
90
|
<ModalBody>
|
|
91
91
|
<Stack gap={2}>
|
|
92
92
|
{#if query}
|
|
93
|
-
{#if commandPaletteManager.
|
|
93
|
+
{#if commandPaletteManager.results.length === 0}
|
|
94
94
|
<Text>{t('search_no_results', translations)}</Text>
|
|
95
95
|
{/if}
|
|
96
96
|
{:else}
|
|
97
97
|
<Text>{t('command_palette_prompt_default', translations)}</Text>
|
|
98
98
|
{/if}
|
|
99
99
|
|
|
100
|
-
{#
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
{#each commandPaletteManager.results as result, groupIndex (result.provider.name ?? groupIndex)}
|
|
101
|
+
{#if result.provider.name}
|
|
102
|
+
<Heading size="tiny" class="pt-2">{result.provider.name}</Heading>
|
|
103
|
+
{/if}
|
|
104
|
+
{#if commandPaletteManager.results.length > 0}
|
|
105
|
+
<div class="flex flex-col">
|
|
106
|
+
{#each result.items as item (item.id)}
|
|
107
|
+
<CommandPaletteItem
|
|
108
|
+
{item}
|
|
109
|
+
selected={commandPaletteManager.isSelected(item)}
|
|
110
|
+
onSelect={() => onClose(item)}
|
|
111
|
+
/>
|
|
112
|
+
{/each}
|
|
113
|
+
</div>
|
|
114
|
+
{/if}
|
|
115
|
+
{/each}
|
|
107
116
|
</Stack>
|
|
108
117
|
</ModalBody>
|
|
109
118
|
<ModalFooter>
|
|
110
119
|
<div class="flex w-full justify-around">
|
|
111
|
-
{#if !query && commandPaletteManager.
|
|
120
|
+
{#if !query && commandPaletteManager.results.length === 0}
|
|
112
121
|
<div class="flex place-items-center gap-1">
|
|
113
122
|
<span class="flex gap-1 rounded bg-gray-300 p-1 dark:bg-gray-500">
|
|
114
123
|
<Icon icon={mdiArrowDown} size="1rem" />
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import type { ActionItem, MaybePromise, TranslationProps } from '../types.js';
|
|
2
2
|
export type CommandPaletteTranslations = TranslationProps<'search_placeholder' | 'search_no_results' | 'command_palette_prompt_default' | 'command_palette_to_select' | 'command_palette_to_close' | 'command_palette_to_navigate' | 'command_palette_to_show_all'>;
|
|
3
3
|
export type ActionProvider = {
|
|
4
|
-
name
|
|
4
|
+
name?: string;
|
|
5
5
|
onSearch: (query?: string) => MaybePromise<ActionItem[]>;
|
|
6
6
|
};
|
|
7
7
|
export declare const defaultProvider: ({ name, actions }: {
|
|
8
|
-
name
|
|
8
|
+
name?: string;
|
|
9
9
|
actions: ActionItem[];
|
|
10
10
|
}) => {
|
|
11
|
-
name: string;
|
|
11
|
+
name: string | undefined;
|
|
12
12
|
onSearch: (query?: string) => ActionItem[];
|
|
13
13
|
};
|
|
14
14
|
declare class CommandPaletteManager {
|
|
15
15
|
#private;
|
|
16
16
|
get isEnabled(): boolean;
|
|
17
|
-
get
|
|
17
|
+
get results(): {
|
|
18
|
+
provider: ActionProvider;
|
|
19
|
+
items: Array<ActionItem & {
|
|
20
|
+
id: string;
|
|
21
|
+
}>;
|
|
22
|
+
}[];
|
|
23
|
+
get selectedItem(): {
|
|
18
24
|
title: string;
|
|
19
25
|
description?: string;
|
|
20
26
|
type?: string;
|
|
@@ -30,11 +36,16 @@ declare class CommandPaletteManager {
|
|
|
30
36
|
};
|
|
31
37
|
} & import("../types.js").IfLike & {
|
|
32
38
|
id: string;
|
|
33
|
-
}
|
|
39
|
+
};
|
|
40
|
+
isSelected(item: {
|
|
41
|
+
id: string;
|
|
42
|
+
}): boolean;
|
|
34
43
|
enable(): void;
|
|
35
44
|
setTranslations(translations?: CommandPaletteTranslations): void;
|
|
36
45
|
queryUpdate(query: string): void;
|
|
37
46
|
open(initialQuery?: string): void;
|
|
47
|
+
navigateUp(): void;
|
|
48
|
+
navigateDown(): void;
|
|
38
49
|
loadAllItems(): void;
|
|
39
50
|
addProvider(provider: ActionProvider): () => void;
|
|
40
51
|
}
|
|
@@ -14,12 +14,21 @@ class CommandPaletteManager {
|
|
|
14
14
|
#providers = [];
|
|
15
15
|
#isEnabled = false;
|
|
16
16
|
#isOpen = false;
|
|
17
|
-
#
|
|
17
|
+
#results = $state([]);
|
|
18
|
+
#selectedGroupIndex = $state(0);
|
|
19
|
+
#selectedItemIndex = $state(0);
|
|
18
20
|
get isEnabled() {
|
|
19
21
|
return this.#isEnabled;
|
|
20
22
|
}
|
|
21
|
-
get
|
|
22
|
-
return this.#
|
|
23
|
+
get results() {
|
|
24
|
+
return this.#results;
|
|
25
|
+
}
|
|
26
|
+
get selectedItem() {
|
|
27
|
+
const group = this.#results[this.#selectedGroupIndex];
|
|
28
|
+
return group?.items[this.#selectedItemIndex];
|
|
29
|
+
}
|
|
30
|
+
isSelected(item) {
|
|
31
|
+
return this.selectedItem?.id === item.id;
|
|
23
32
|
}
|
|
24
33
|
enable() {
|
|
25
34
|
if (this.#isEnabled) {
|
|
@@ -39,15 +48,20 @@ class CommandPaletteManager {
|
|
|
39
48
|
this.#translations = translations;
|
|
40
49
|
}
|
|
41
50
|
async #onSearch(query) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
const newResults = await Promise.all(this.#providers.map(async (provider) => {
|
|
52
|
+
const items = await provider.onSearch(query);
|
|
53
|
+
return {
|
|
54
|
+
provider,
|
|
55
|
+
items: items.filter((item) => isEnabled(item)).map((item) => ({ ...item, id: generateId() })),
|
|
56
|
+
};
|
|
57
|
+
}));
|
|
58
|
+
this.#selectedGroupIndex = 0;
|
|
59
|
+
this.#selectedItemIndex = 0;
|
|
60
|
+
this.#results = newResults.filter((result) => result.items.length > 0);
|
|
47
61
|
}
|
|
48
62
|
queryUpdate(query) {
|
|
49
63
|
if (!query) {
|
|
50
|
-
this.#
|
|
64
|
+
this.#results = [];
|
|
51
65
|
return;
|
|
52
66
|
}
|
|
53
67
|
void this.#onSearch(query);
|
|
@@ -78,7 +92,7 @@ class CommandPaletteManager {
|
|
|
78
92
|
async #onClose(action) {
|
|
79
93
|
await action?.onAction(action);
|
|
80
94
|
this.#isOpen = false;
|
|
81
|
-
this.#
|
|
95
|
+
this.#results = [];
|
|
82
96
|
}
|
|
83
97
|
open(initialQuery) {
|
|
84
98
|
if (this.#isOpen) {
|
|
@@ -91,6 +105,35 @@ class CommandPaletteManager {
|
|
|
91
105
|
this.#isOpen = true;
|
|
92
106
|
void onClose.then((action) => this.#onClose(action));
|
|
93
107
|
}
|
|
108
|
+
navigateUp() {
|
|
109
|
+
const groups = this.#results;
|
|
110
|
+
if (groups.length === 0) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.#selectedItemIndex--;
|
|
114
|
+
if (this.#selectedItemIndex < 0) {
|
|
115
|
+
this.#selectedGroupIndex--; // previous group
|
|
116
|
+
if (this.#selectedGroupIndex < 0) {
|
|
117
|
+
this.#selectedGroupIndex = groups.length - 1; // first group
|
|
118
|
+
}
|
|
119
|
+
this.#selectedItemIndex = groups[this.#selectedGroupIndex].items.length - 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
navigateDown() {
|
|
123
|
+
const groups = this.#results;
|
|
124
|
+
if (groups.length === 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const group = groups[this.#selectedGroupIndex];
|
|
128
|
+
this.#selectedItemIndex++;
|
|
129
|
+
if (this.#selectedItemIndex >= group.items.length) {
|
|
130
|
+
this.#selectedItemIndex = 0;
|
|
131
|
+
this.#selectedGroupIndex++; // next group
|
|
132
|
+
if (this.#selectedGroupIndex >= groups.length) {
|
|
133
|
+
this.#selectedGroupIndex = 0; // first group
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
94
137
|
loadAllItems() {
|
|
95
138
|
void this.#onSearch();
|
|
96
139
|
}
|
package/dist/site/constants.d.ts
CHANGED
package/dist/site/constants.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@immich/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.1",
|
|
4
4
|
"license": "GNU Affero General Public License version 3",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@immich/svelte-markdown-preprocess": "^0.2.1"
|
|
59
59
|
},
|
|
60
60
|
"volta": {
|
|
61
|
-
"node": "24.
|
|
61
|
+
"node": "24.14.0"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"create": "node scripts/create.js",
|