@immich/ui 0.58.1 → 0.58.3
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/Modal/Modal.svelte +19 -11
- package/dist/components/MultiSelect/MultiSelect.svelte +8 -4
- package/dist/components/Select/Select.svelte +8 -4
- package/dist/internal/Select.svelte +15 -10
- package/dist/internal/Select.svelte.d.ts +3 -2
- package/dist/services/modal-manager.svelte.d.ts +1 -1
- package/dist/services/modal-manager.svelte.js +2 -3
- package/dist/services/toast-manager.svelte.d.ts +1 -0
- package/dist/services/toast-manager.svelte.js +3 -0
- package/dist/site/constants.d.ts +1 -1
- package/dist/site/constants.js +2 -3
- package/dist/state/modal-state.svelte.d.ts +8 -0
- package/dist/state/modal-state.svelte.js +16 -0
- package/dist/types.d.ts +6 -4
- package/dist/utilities/common.d.ts +1 -0
- package/dist/utilities/common.js +11 -0
- package/package.json +1 -1
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
import Logo from '../Logo/Logo.svelte';
|
|
11
11
|
import TooltipProvider from '../Tooltip/TooltipProvider.svelte';
|
|
12
12
|
import { ChildKey, zIndex } from '../../constants.js';
|
|
13
|
+
import { modalState } from '../../state/modal-state.svelte.js';
|
|
13
14
|
import type { ModalSize } from '../../types.js';
|
|
14
15
|
import { cleanClass } from '../../utilities/internal.js';
|
|
15
16
|
import { Dialog } from 'bits-ui';
|
|
16
|
-
import {
|
|
17
|
+
import { onMount, type Snippet } from 'svelte';
|
|
17
18
|
import { tv } from 'tailwind-variants';
|
|
18
19
|
|
|
19
20
|
type Props = {
|
|
@@ -74,21 +75,28 @@
|
|
|
74
75
|
const bodyChildren = $derived(getChildSnippet(ChildKey.ModalBody));
|
|
75
76
|
const footerChildren = $derived(getChildSnippet(ChildKey.ModalFooter));
|
|
76
77
|
|
|
77
|
-
const handleClose = async () => {
|
|
78
|
-
// wait for bits-ui to complete its event cycle
|
|
79
|
-
await tick();
|
|
80
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
81
|
-
|
|
82
|
-
onClose?.();
|
|
83
|
-
};
|
|
84
|
-
|
|
85
78
|
let cardRef = $state<HTMLElement | null>(null);
|
|
86
79
|
|
|
87
80
|
const interactOutsideBehavior = $derived(closeOnBackdropClick ? 'close' : 'ignore');
|
|
88
81
|
const escapeKeydownBehavior = $derived(closeOnEsc ? 'close' : 'ignore');
|
|
82
|
+
|
|
83
|
+
let layer = $state<number>();
|
|
84
|
+
const isHidden = $derived(layer !== modalState.layer);
|
|
85
|
+
|
|
86
|
+
const onOpenChangeComplete = (isOpen: boolean) => {
|
|
87
|
+
if (!isOpen && !isHidden) {
|
|
88
|
+
onClose?.();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
onMount(() => {
|
|
93
|
+
layer = modalState.incrementLayer();
|
|
94
|
+
|
|
95
|
+
return () => modalState.decrementLayer();
|
|
96
|
+
});
|
|
89
97
|
</script>
|
|
90
98
|
|
|
91
|
-
<Dialog.Root open={
|
|
99
|
+
<Dialog.Root open={!isHidden} {onOpenChangeComplete}>
|
|
92
100
|
<Dialog.Portal>
|
|
93
101
|
<Dialog.Overlay class="{zIndex.ModalBackdrop} fixed start-0 top-0 flex h-dvh max-h-dvh w-screen bg-black/30" />
|
|
94
102
|
<Dialog.Content
|
|
@@ -111,7 +119,7 @@
|
|
|
111
119
|
<Logo variant="icon" size="tiny" />
|
|
112
120
|
{/if}
|
|
113
121
|
<CardTitle tag="p" class="text-dark/90 grow text-lg font-semibold">{title}</CardTitle>
|
|
114
|
-
<CloseButton class="-me-2" onclick={() =>
|
|
122
|
+
<CloseButton class="-me-2" onclick={() => onClose?.()} />
|
|
115
123
|
</div>
|
|
116
124
|
{/if}
|
|
117
125
|
</CardHeader>
|
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
import InternalSelect from '../../internal/Select.svelte';
|
|
3
3
|
import type { MultiSelectProps, SelectItem } from '../../types.js';
|
|
4
4
|
|
|
5
|
-
let { onChange, values = $bindable([]), ...restProps }: MultiSelectProps<T> = $props();
|
|
5
|
+
let { onChange, onItemChange, values = $bindable([]), ...restProps }: MultiSelectProps<T> = $props();
|
|
6
6
|
|
|
7
|
-
const handleChange = (
|
|
8
|
-
onChange?.(
|
|
7
|
+
const handleChange = (values: T[]) => {
|
|
8
|
+
onChange?.(values);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const handleItemChange = (items: SelectItem<T>[]) => {
|
|
12
|
+
onItemChange?.(items);
|
|
9
13
|
};
|
|
10
14
|
</script>
|
|
11
15
|
|
|
12
|
-
<InternalSelect multiple bind:values onChange={handleChange} {...restProps} />
|
|
16
|
+
<InternalSelect multiple bind:values onItemChange={handleItemChange} onChange={handleChange} {...restProps} />
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
import InternalSelect from '../../internal/Select.svelte';
|
|
3
3
|
import type { SelectItem, SelectProps } from '../../types.js';
|
|
4
4
|
|
|
5
|
-
let { onChange, value = $bindable(), ...restProps }: SelectProps<T> = $props();
|
|
5
|
+
let { onChange, onItemChange, value = $bindable(), ...restProps }: SelectProps<T> = $props();
|
|
6
6
|
|
|
7
7
|
let values = $derived(value ? [value] : []);
|
|
8
8
|
|
|
9
|
-
const handleChange = (
|
|
10
|
-
value =
|
|
9
|
+
const handleChange = (values: T[]) => {
|
|
10
|
+
value = values[0];
|
|
11
11
|
onChange?.(value);
|
|
12
12
|
};
|
|
13
|
+
|
|
14
|
+
const handleItemChange = (items: SelectItem<T>[]) => {
|
|
15
|
+
onItemChange?.(items[0]);
|
|
16
|
+
};
|
|
13
17
|
</script>
|
|
14
18
|
|
|
15
|
-
<InternalSelect bind:values onChange={handleChange} {...restProps} />
|
|
19
|
+
<InternalSelect bind:values onChange={handleChange} onItemChange={handleItemChange} {...restProps} />
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
type Props = {
|
|
14
14
|
multiple?: boolean;
|
|
15
|
-
values:
|
|
15
|
+
values: T[];
|
|
16
16
|
asLabel?: (items: SelectItem<T>[]) => string;
|
|
17
|
-
onChange?: (values:
|
|
17
|
+
onChange?: (values: T[]) => void;
|
|
18
|
+
onItemChange?: (items: SelectItem<T>[]) => void;
|
|
18
19
|
} & SelectCommonProps<T>;
|
|
19
20
|
|
|
20
21
|
let {
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
multiple = false,
|
|
25
26
|
values = $bindable([]),
|
|
26
27
|
onChange,
|
|
28
|
+
onItemChange,
|
|
27
29
|
asLabel = (options: SelectItem<T>[]) => options.map(({ label }) => label).join(', '),
|
|
28
30
|
placeholder,
|
|
29
31
|
class: className,
|
|
@@ -40,13 +42,16 @@
|
|
|
40
42
|
});
|
|
41
43
|
};
|
|
42
44
|
|
|
43
|
-
const options = $derived(asOptions(data));
|
|
44
|
-
|
|
45
45
|
const context = getFieldContext();
|
|
46
46
|
const { invalid, disabled, ...labelProps } = $derived(context());
|
|
47
47
|
const size = $derived(initialSize ?? labelProps.size ?? 'small');
|
|
48
|
+
const options = $derived(asOptions(data));
|
|
48
49
|
|
|
49
|
-
const
|
|
50
|
+
const findOption = (value: string) => options.find((option) => option.value === value);
|
|
51
|
+
const valuesToOptions = (values: T[]) =>
|
|
52
|
+
values.map(findOption).filter((item): item is SelectItem<T> => Boolean(item));
|
|
53
|
+
|
|
54
|
+
const selectedLabel = $derived(asLabel(valuesToOptions(values)));
|
|
50
55
|
|
|
51
56
|
const triggerStyles = tv({
|
|
52
57
|
base: 'w-full gap-1 rounded-lg py-0 text-start focus-visible:outline-none',
|
|
@@ -68,15 +73,15 @@
|
|
|
68
73
|
}
|
|
69
74
|
});
|
|
70
75
|
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
values = (Array.isArray(items) ? items : [items]).map(findOption).filter((item) => item !== undefined);
|
|
76
|
+
const onValueChange = (newValues: string[] | string) => {
|
|
77
|
+
values = (Array.isArray(newValues) ? newValues : [newValues]) as T[];
|
|
78
|
+
const items = values.map((value) => findOption(value)).filter((item): item is SelectItem<T> => item !== undefined);
|
|
75
79
|
|
|
76
80
|
onChange?.(values);
|
|
81
|
+
onItemChange?.(items);
|
|
77
82
|
};
|
|
78
83
|
|
|
79
|
-
let internalValue = $derived(multiple ? values
|
|
84
|
+
let internalValue = $derived(multiple ? values : (values[0] ?? ''));
|
|
80
85
|
</script>
|
|
81
86
|
|
|
82
87
|
<div class={cleanClass('flex flex-col gap-1', className)} bind:this={ref}>
|
|
@@ -3,9 +3,10 @@ import { Select } from 'bits-ui';
|
|
|
3
3
|
declare function $$render<T extends string>(): {
|
|
4
4
|
props: {
|
|
5
5
|
multiple?: boolean;
|
|
6
|
-
values:
|
|
6
|
+
values: T[];
|
|
7
7
|
asLabel?: (items: SelectItem<T>[]) => string;
|
|
8
|
-
onChange?: (values:
|
|
8
|
+
onChange?: (values: T[]) => void;
|
|
9
|
+
onItemChange?: (items: SelectItem<T>[]) => void;
|
|
9
10
|
} & SelectCommonProps<T>;
|
|
10
11
|
exports: {};
|
|
11
12
|
bindings: "values";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Component, type ComponentProps } from 'svelte';
|
|
2
1
|
import ConfirmModal from '../components/ConfirmModal/ConfirmModal.svelte';
|
|
2
|
+
import { type Component, type ComponentProps } from 'svelte';
|
|
3
3
|
type OnCloseData<T> = T extends {
|
|
4
4
|
onClose: (data: infer R) => void;
|
|
5
5
|
} ? unknown extends R ? void : R : T extends {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mount, unmount } from 'svelte';
|
|
2
1
|
import ConfirmModal from '../components/ConfirmModal/ConfirmModal.svelte';
|
|
2
|
+
import { mount, unmount } from 'svelte';
|
|
3
3
|
class ModalManager {
|
|
4
4
|
show(Component, ...props) {
|
|
5
5
|
return this.open(Component, ...props).onClose;
|
|
@@ -10,8 +10,7 @@ class ModalManager {
|
|
|
10
10
|
const deferred = new Promise((resolve) => {
|
|
11
11
|
onClose = async (...args) => {
|
|
12
12
|
await unmount(modal);
|
|
13
|
-
|
|
14
|
-
setTimeout(() => resolve(args[0]), 10);
|
|
13
|
+
resolve(args[0]);
|
|
15
14
|
};
|
|
16
15
|
modal = mount(Component, {
|
|
17
16
|
target: document.body,
|
|
@@ -8,6 +8,7 @@ declare class ToastManager {
|
|
|
8
8
|
open(item: ToastItem, options?: ToastOptions): void;
|
|
9
9
|
mount(): Promise<void>;
|
|
10
10
|
unmount(): Promise<void>;
|
|
11
|
+
primary(item?: string | ToastShow, options?: ToastOptions): void;
|
|
11
12
|
success(item?: string | ToastShow, options?: ToastOptions): void;
|
|
12
13
|
info(item?: string | ToastShow, options?: ToastOptions): void;
|
|
13
14
|
warning(item?: string | ToastShow, options?: ToastOptions): void;
|
|
@@ -48,6 +48,9 @@ class ToastManager {
|
|
|
48
48
|
await unmount(this.#ref);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
primary(item, options) {
|
|
52
|
+
this.show({ title: t('toast_success_title'), color: 'primary', ...expand(item) }, options);
|
|
53
|
+
}
|
|
51
54
|
success(item, options) {
|
|
52
55
|
this.show({ title: t('toast_success_title'), color: 'success', ...expand(item) }, options);
|
|
53
56
|
}
|
package/dist/site/constants.d.ts
CHANGED
package/dist/site/constants.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { asText } from '../utilities/common.js';
|
|
1
|
+
import { asText, navigateTo } from '../utilities/common.js';
|
|
3
2
|
import { mdiOpenInNew } from '@mdi/js';
|
|
4
3
|
export const Constants = {
|
|
5
4
|
Socials: {
|
|
@@ -134,6 +133,6 @@ export const siteCommands = [
|
|
|
134
133
|
iconClass: 'text-indigo-700 dark:text-indigo-200',
|
|
135
134
|
title: site.title,
|
|
136
135
|
description: site.description,
|
|
137
|
-
onAction: () =>
|
|
136
|
+
onAction: () => navigateTo(site.href),
|
|
138
137
|
searchText: asText('Site', 'Link', site.title, site.description, site.href),
|
|
139
138
|
}));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class ModalState {
|
|
2
|
+
#layer = $state(0);
|
|
3
|
+
get layer() {
|
|
4
|
+
return this.#layer;
|
|
5
|
+
}
|
|
6
|
+
incrementLayer() {
|
|
7
|
+
return ++this.#layer;
|
|
8
|
+
}
|
|
9
|
+
decrementLayer() {
|
|
10
|
+
if (this.#layer < 1) {
|
|
11
|
+
throw new Error('Tried to decrement the modal layer <0');
|
|
12
|
+
}
|
|
13
|
+
return --this.#layer;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export const modalState = new ModalState();
|
package/dist/types.d.ts
CHANGED
|
@@ -165,12 +165,14 @@ export type SelectCommonProps<T extends string> = {
|
|
|
165
165
|
class?: string;
|
|
166
166
|
};
|
|
167
167
|
export type SelectProps<T extends string> = SelectCommonProps<T> & {
|
|
168
|
-
value?:
|
|
169
|
-
onChange?: (value:
|
|
168
|
+
value?: T;
|
|
169
|
+
onChange?: (value: T) => void;
|
|
170
|
+
onItemChange?: (item: SelectItem<T>) => void;
|
|
170
171
|
};
|
|
171
172
|
export type MultiSelectProps<T extends string> = SelectCommonProps<T> & {
|
|
172
|
-
values?:
|
|
173
|
-
onChange?: (values:
|
|
173
|
+
values?: T[];
|
|
174
|
+
onChange?: (values: T[]) => void;
|
|
175
|
+
onItemChange?: (items: SelectItem<T>[]) => void;
|
|
174
176
|
};
|
|
175
177
|
export type ToastId = {
|
|
176
178
|
id: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MenuItemType, type ActionItem, type IfLike } from '../types.js';
|
|
2
2
|
import type { DateTime } from 'luxon';
|
|
3
|
+
export declare const navigateTo: (url: string) => Promise<void>;
|
|
3
4
|
export declare const resolveUrl: (url: string, currentHostname?: string) => string;
|
|
4
5
|
export declare const isExternalLink: (href: string) => boolean;
|
|
5
6
|
export type Metadata = {
|
package/dist/utilities/common.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { goto } from '$app/navigation';
|
|
1
2
|
import { env } from '$env/dynamic/public';
|
|
2
3
|
import { MenuItemType } from '../types.js';
|
|
3
4
|
const getImmichApp = (host) => {
|
|
@@ -9,6 +10,16 @@ const getImmichApp = (host) => {
|
|
|
9
10
|
}
|
|
10
11
|
return host.split('.')[0];
|
|
11
12
|
};
|
|
13
|
+
export const navigateTo = async (url) => {
|
|
14
|
+
const resolvedUrl = resolveUrl(url);
|
|
15
|
+
const external = isExternalLink(resolvedUrl);
|
|
16
|
+
if (external) {
|
|
17
|
+
window.open(resolvedUrl, '_blank', 'noreferrer');
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
await goto(resolvedUrl);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
12
23
|
export const resolveUrl = (url, currentHostname) => {
|
|
13
24
|
if (!isExternalLink(url)) {
|
|
14
25
|
return url;
|