@immich/ui 0.58.0 → 0.58.2

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.
@@ -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 { tick, type Snippet } from 'svelte';
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={true} onOpenChange={(isOpen: boolean) => !isOpen && handleClose()}>
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={() => handleClose()} />
122
+ <CloseButton class="-me-2" onclick={() => onClose?.()} />
115
123
  </div>
116
124
  {/if}
117
125
  </CardHeader>
@@ -1,12 +1,10 @@
1
- <script lang="ts">
1
+ <script lang="ts" generics="T extends string">
2
2
  import InternalSelect from '../../internal/Select.svelte';
3
3
  import type { MultiSelectProps, SelectItem } from '../../types.js';
4
4
 
5
- type T = SelectItem;
6
-
7
5
  let { onChange, values = $bindable([]), ...restProps }: MultiSelectProps<T> = $props();
8
6
 
9
- const handleChange = (items: T[]) => {
7
+ const handleChange = (items: SelectItem<T>[]) => {
10
8
  onChange?.(items);
11
9
  };
12
10
  </script>
@@ -1,4 +1,25 @@
1
- import type { MultiSelectProps, SelectItem } from '../../types.js';
2
- declare const MultiSelect: import("svelte").Component<MultiSelectProps<SelectItem>, {}, "values">;
3
- type MultiSelect = ReturnType<typeof MultiSelect>;
1
+ import type { MultiSelectProps } from '../../types.js';
2
+ declare function $$render<T extends string>(): {
3
+ props: MultiSelectProps<T>;
4
+ exports: {};
5
+ bindings: "values";
6
+ slots: {};
7
+ events: {};
8
+ };
9
+ declare class __sveltets_Render<T extends string> {
10
+ props(): ReturnType<typeof $$render<T>>['props'];
11
+ events(): ReturnType<typeof $$render<T>>['events'];
12
+ slots(): ReturnType<typeof $$render<T>>['slots'];
13
+ bindings(): "values";
14
+ exports(): {};
15
+ }
16
+ interface $$IsomorphicComponent {
17
+ new <T extends string>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
18
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
19
+ } & ReturnType<__sveltets_Render<T>['exports']>;
20
+ <T extends string>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
21
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
22
+ }
23
+ declare const MultiSelect: $$IsomorphicComponent;
24
+ type MultiSelect<T extends string> = InstanceType<typeof MultiSelect<T>>;
4
25
  export default MultiSelect;
@@ -1,14 +1,12 @@
1
- <script lang="ts">
1
+ <script lang="ts" generics="T extends string">
2
2
  import InternalSelect from '../../internal/Select.svelte';
3
3
  import type { SelectItem, SelectProps } from '../../types.js';
4
4
 
5
- type T = SelectItem;
6
-
7
5
  let { onChange, value = $bindable(), ...restProps }: SelectProps<T> = $props();
8
6
 
9
7
  let values = $derived(value ? [value] : []);
10
8
 
11
- const handleChange = (items: T[]) => {
9
+ const handleChange = (items: SelectItem<T>[]) => {
12
10
  value = items[0];
13
11
  onChange?.(value);
14
12
  };
@@ -1,4 +1,25 @@
1
- import type { SelectItem, SelectProps } from '../../types.js';
2
- declare const Select: import("svelte").Component<SelectProps<SelectItem>, {}, "value">;
3
- type Select = ReturnType<typeof Select>;
1
+ import type { SelectProps } from '../../types.js';
2
+ declare function $$render<T extends string>(): {
3
+ props: SelectProps<T>;
4
+ exports: {};
5
+ bindings: "value";
6
+ slots: {};
7
+ events: {};
8
+ };
9
+ declare class __sveltets_Render<T extends string> {
10
+ props(): ReturnType<typeof $$render<T>>['props'];
11
+ events(): ReturnType<typeof $$render<T>>['events'];
12
+ slots(): ReturnType<typeof $$render<T>>['slots'];
13
+ bindings(): "value";
14
+ exports(): {};
15
+ }
16
+ interface $$IsomorphicComponent {
17
+ new <T extends string>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
18
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
19
+ } & ReturnType<__sveltets_Render<T>['exports']>;
20
+ <T extends string>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
21
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
22
+ }
23
+ declare const Select: $$IsomorphicComponent;
24
+ type Select<T extends string> = InstanceType<typeof Select<T>>;
4
25
  export default Select;
@@ -1,4 +1,4 @@
1
- <script lang="ts">
1
+ <script lang="ts" generics="T extends string">
2
2
  import { getFieldContext } from '../common/context.svelte.js';
3
3
  import Icon from '../components/Icon/Icon.svelte';
4
4
  import IconButton from '../components/IconButton/IconButton.svelte';
@@ -10,13 +10,11 @@
10
10
  import { Select } from 'bits-ui';
11
11
  import { tv } from 'tailwind-variants';
12
12
 
13
- type T = SelectItem;
14
-
15
13
  type Props = {
16
14
  multiple?: boolean;
17
- values: T[];
18
- asLabel?: (items: T[]) => string;
19
- onChange?: (values: T[]) => void;
15
+ values: SelectItem<T>[];
16
+ asLabel?: (items: SelectItem<T>[]) => string;
17
+ onChange?: (values: SelectItem<T>[]) => void;
20
18
  } & SelectCommonProps<T>;
21
19
 
22
20
  let {
@@ -26,15 +24,15 @@
26
24
  multiple = false,
27
25
  values = $bindable([]),
28
26
  onChange,
29
- asLabel = (options: T[]) => options.map(({ label }) => label).join(', '),
27
+ asLabel = (options: SelectItem<T>[]) => options.map(({ label }) => label).join(', '),
30
28
  placeholder,
31
29
  class: className,
32
30
  }: Props = $props();
33
31
 
34
- const asOptions = (items: string[] | T[]) => {
32
+ const asOptions = (items: string[] | SelectItem<T>[]) => {
35
33
  return items.map((item) => {
36
34
  if (typeof item === 'string') {
37
- return { value: item, label: item } as T;
35
+ return { value: item, label: item } as SelectItem<T>;
38
36
  }
39
37
 
40
38
  const label = item.label ?? item.value;
@@ -73,9 +71,7 @@
73
71
  const findOption = (value: string) => options.find((option) => option.value === value);
74
72
 
75
73
  const onValueChange = (items: string[] | string) => {
76
- values = multiple
77
- ? ((items as string[]).map(findOption) as T[])
78
- : [findOption(items as string) as T].filter(Boolean);
74
+ values = (Array.isArray(items) ? items : [items]).map(findOption).filter((item) => item !== undefined);
79
75
 
80
76
  onChange?.(values);
81
77
  };
@@ -1,12 +1,31 @@
1
1
  import type { SelectCommonProps, SelectItem } from '../types.js';
2
2
  import { Select } from 'bits-ui';
3
- type T = SelectItem;
4
- type Props = {
5
- multiple?: boolean;
6
- values: T[];
7
- asLabel?: (items: T[]) => string;
8
- onChange?: (values: T[]) => void;
9
- } & SelectCommonProps<T>;
10
- declare const Select: import("svelte").Component<Props, {}, "values">;
11
- type Select = ReturnType<typeof Select>;
3
+ declare function $$render<T extends string>(): {
4
+ props: {
5
+ multiple?: boolean;
6
+ values: SelectItem<T>[];
7
+ asLabel?: (items: SelectItem<T>[]) => string;
8
+ onChange?: (values: SelectItem<T>[]) => void;
9
+ } & SelectCommonProps<T>;
10
+ exports: {};
11
+ bindings: "values";
12
+ slots: {};
13
+ events: {};
14
+ };
15
+ declare class __sveltets_Render<T extends string> {
16
+ props(): ReturnType<typeof $$render<T>>['props'];
17
+ events(): ReturnType<typeof $$render<T>>['events'];
18
+ slots(): ReturnType<typeof $$render<T>>['slots'];
19
+ bindings(): "values";
20
+ exports(): {};
21
+ }
22
+ interface $$IsomorphicComponent {
23
+ new <T extends string>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
24
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
25
+ } & ReturnType<__sveltets_Render<T>['exports']>;
26
+ <T extends string>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
27
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
28
+ }
29
+ declare const Select: $$IsomorphicComponent;
30
+ type Select<T extends string> = InstanceType<typeof Select<T>>;
12
31
  export default Select;
@@ -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
- // make sure bits-ui clean up finishes before resolving
14
- setTimeout(() => resolve(args[0]), 10);
13
+ resolve(args[0]);
15
14
  };
16
15
  modal = mount(Component, {
17
16
  target: document.body,
@@ -45,6 +45,6 @@ export declare const siteCommands: {
45
45
  iconClass: string;
46
46
  title: string;
47
47
  description: string;
48
- onAction: () => any;
48
+ onAction: () => Promise<void>;
49
49
  searchText: string;
50
50
  }[];
@@ -1,5 +1,4 @@
1
- import { goto } from '$app/navigation';
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: () => goto(site.href),
136
+ onAction: () => navigateTo(site.href),
138
137
  searchText: asText('Site', 'Link', site.title, site.description, site.href),
139
138
  }));
@@ -0,0 +1,8 @@
1
+ declare class ModalState {
2
+ #private;
3
+ get layer(): number;
4
+ incrementLayer(): number;
5
+ decrementLayer(): number;
6
+ }
7
+ export declare const modalState: ModalState;
8
+ export {};
@@ -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
@@ -152,25 +152,25 @@ export type TextareaProps = {
152
152
  shape?: Shape;
153
153
  grow?: boolean;
154
154
  } & HTMLTextareaAttributes;
155
- export type SelectItem = {
155
+ export type SelectItem<T extends string = string> = {
156
156
  label?: string;
157
- value: string;
157
+ value: T;
158
158
  disabled?: boolean;
159
159
  };
160
- export type SelectCommonProps<T extends SelectItem> = {
161
- data: string[] | T[];
160
+ export type SelectCommonProps<T extends string> = {
161
+ data: string[] | SelectItem<T>[];
162
162
  size?: Size;
163
163
  shape?: Shape;
164
164
  placeholder?: string;
165
165
  class?: string;
166
166
  };
167
- export type SelectProps<T extends SelectItem> = SelectCommonProps<T> & {
168
- value?: T;
169
- onChange?: (value: T) => void;
167
+ export type SelectProps<T extends string> = SelectCommonProps<T> & {
168
+ value?: SelectItem<T>;
169
+ onChange?: (value: SelectItem<T>) => void;
170
170
  };
171
- export type MultiSelectProps<T extends SelectItem> = SelectCommonProps<T> & {
172
- values?: T[];
173
- onChange?: (values: T[]) => void;
171
+ export type MultiSelectProps<T extends string> = SelectCommonProps<T> & {
172
+ values?: SelectItem<T>[];
173
+ onChange?: (values: SelectItem<T>[]) => void;
174
174
  };
175
175
  export type ToastId = {
176
176
  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 = {
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/ui",
3
- "version": "0.58.0",
3
+ "version": "0.58.2",
4
4
  "license": "GNU Affero General Public License version 3",
5
5
  "repository": {
6
6
  "type": "git",