@immich/ui 0.64.1 → 0.65.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.
@@ -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}>
@@ -5,6 +5,8 @@ type Props = {
5
5
  icon?: string | boolean;
6
6
  closeText?: string;
7
7
  closeColor?: Color;
8
+ closeOnBackdropClick?: boolean;
9
+ closeOnEsc?: boolean;
8
10
  size?: ModalSize;
9
11
  onClose: () => void;
10
12
  children: Snippet;
@@ -4,7 +4,7 @@
4
4
  import type { ActionItem } from '../../types.js';
5
5
 
6
6
  type Props = {
7
- name: string;
7
+ name?: string;
8
8
  actions?: ActionItem[];
9
9
  };
10
10
 
@@ -1,6 +1,6 @@
1
1
  import type { ActionItem } from '../../types.js';
2
2
  type Props = {
3
- name: string;
3
+ name?: string;
4
4
  actions?: ActionItem[];
5
5
  };
6
6
  declare const CommandPaletteDefaultProvider: import("svelte").Component<Props, {}, "">;
@@ -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" class={cleanClass(selected && 'border')}>{shortcut.join(' ')}</Kbd>
65
+ <Kbd size="tiny">{shortcut.join(' ')}</Kbd>
67
66
  </div>
68
67
  {/each}
69
68
  </div>
@@ -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
- selectedIndex = Math.max((selectedIndex === 0 ? commandPaletteManager.items.length : selectedIndex) - 1, 0);
42
+ commandPaletteManager.navigateUp();
38
43
  break;
39
44
  }
40
45
 
41
46
  case 'down': {
42
- if (!query && commandPaletteManager.items.length === 0) {
47
+ if (!query && commandPaletteManager.results.length === 0) {
43
48
  commandPaletteManager.loadAllItems();
44
49
  break;
45
50
  }
46
51
 
47
- selectedIndex = (selectedIndex + 1) % commandPaletteManager.items.length || 0;
52
+ commandPaletteManager.navigateDown();
48
53
  break;
49
54
  }
50
55
 
51
56
  case 'select': {
52
- onClose(commandPaletteManager.items[selectedIndex]);
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
@@ -90,25 +90,34 @@
90
90
  <ModalBody>
91
91
  <Stack gap={2}>
92
92
  {#if query}
93
- {#if commandPaletteManager.items.length === 0}
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
- {#if commandPaletteManager.items.length > 0}
101
- <div class="flex flex-col">
102
- {#each commandPaletteManager.items as item, i (item.id)}
103
- <CommandPaletteItem {item} selected={selectedIndex === i} onSelect={() => onClose(item)} />
104
- {/each}
105
- </div>
106
- {/if}
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.items.length === 0}
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: string;
4
+ name?: string;
5
5
  onSearch: (query?: string) => MaybePromise<ActionItem[]>;
6
6
  };
7
7
  export declare const defaultProvider: ({ name, actions }: {
8
- name: string;
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 items(): ({
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
- #items = $state([]);
17
+ #results = $state([]);
18
+ #selectedGroupIndex = $state(0);
19
+ #selectedItemIndex = $state(0);
18
20
  get isEnabled() {
19
21
  return this.#isEnabled;
20
22
  }
21
- get items() {
22
- return this.#items;
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 newItems = await Promise.all(this.#providers.map((provider) => Promise.resolve(provider.onSearch(query))));
43
- this.#items = newItems
44
- .flat()
45
- .filter((item) => isEnabled(item))
46
- .map((item) => ({ ...item, id: generateId() }));
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.#items = [];
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.#items = [];
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
  }
@@ -41,7 +41,6 @@ export declare const Constants: {
41
41
  };
42
42
  export declare const siteCommands: {
43
43
  icon: string;
44
- type: string;
45
44
  iconClass: string;
46
45
  title: string;
47
46
  description: string;
@@ -129,7 +129,6 @@ export const siteCommands = [
129
129
  },
130
130
  ].map((site) => ({
131
131
  icon: mdiOpenInNew,
132
- type: 'Link',
133
132
  iconClass: 'text-indigo-700 dark:text-indigo-200',
134
133
  title: site.title,
135
134
  description: site.description,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/ui",
3
- "version": "0.64.1",
3
+ "version": "0.65.0",
4
4
  "license": "GNU Affero General Public License version 3",
5
5
  "repository": {
6
6
  "type": "git",