@axium/client 0.17.3 → 0.18.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.
@@ -16,15 +16,54 @@
16
16
  }
17
17
  }
18
18
 
19
- :root {
20
- --A-zoom: zoom 0.25s cubic-bezier(0.35, 1.55, 0.65, 1);
21
- --A-fade: fade 0.25s ease-out;
19
+ @keyframes fade-subtle {
20
+ from {
21
+ opacity: 0.75;
22
+ }
23
+ to {
24
+ opacity: 1;
25
+ }
26
+ }
27
+
28
+ @keyframes slide-up {
29
+ from {
30
+ translate: 0 var(--A-slide-amount, 100%);
31
+ }
32
+ to {
33
+ translate: 0 0;
34
+ }
22
35
  }
23
36
 
24
- dialog[open] {
37
+ @keyframes slide-right {
38
+ from {
39
+ translate: 0 0;
40
+ }
41
+ to {
42
+ translate: var(--A-slide-amount, 100%) 0;
43
+ }
44
+ }
45
+
46
+ @media not (prefers-reduced-motion) {
47
+ :root {
48
+ /* CSS variables to make using the animations easier.
49
+ Prefixed with `A-` to avoid namespace pollution. */
50
+ --A-zoom: zoom 0.25s cubic-bezier(0.35, 1.55, 0.65, 1);
51
+ --A-fade: fade 0.25s ease-out;
52
+ --A-slide-up: slide-up 0.25s ease;
53
+ --A-slide-right: slide-right 0.25s ease;
54
+ --A-slide-out-right: slide-right 0.25s ease forwards, var(--A-fade) reverse forwards;
55
+ }
56
+ }
57
+
58
+ dialog:open {
25
59
  animation: var(--A-zoom);
26
60
  }
27
61
 
28
- dialog[open]::backdrop {
62
+ dialog:open::backdrop {
29
63
  animation: var(--A-fade);
30
64
  }
65
+
66
+ .toast {
67
+ --A-slide-amount: 100px;
68
+ animation: var(--A-slide-up);
69
+ }
package/assets/styles.css CHANGED
@@ -68,11 +68,23 @@ select {
68
68
  padding: 0.5em 1em;
69
69
  }
70
70
 
71
+ button {
72
+ cursor: pointer;
73
+
74
+ &:hover {
75
+ background-color: hsl(var(--hue) 15 calc(var(--bg-light) + (var(--light-step) * 2)));
76
+ }
77
+ }
78
+
71
79
  button.reset {
72
80
  border-radius: unset;
73
81
  border: none;
74
82
  background-color: unset;
75
83
  padding: unset;
84
+
85
+ &:hover {
86
+ background-color: unset;
87
+ }
76
88
  }
77
89
 
78
90
  input,
@@ -116,14 +128,6 @@ option:hover {
116
128
  background-color: var(--bg-alt);
117
129
  }
118
130
 
119
- button {
120
- cursor: pointer;
121
- }
122
-
123
- button:hover {
124
- background-color: hsl(var(--hue) 15 calc(var(--bg-light) + (var(--light-step) * 2)));
125
- }
126
-
127
131
  code,
128
132
  pre {
129
133
  background-color: var(--bg-menu);
@@ -144,7 +148,7 @@ dialog {
144
148
  }
145
149
  }
146
150
 
147
- dialog::backdrop {
151
+ dialog:modal::backdrop {
148
152
  background: #0003;
149
153
  }
150
154
 
@@ -178,24 +182,33 @@ progress::-moz-progress-bar {
178
182
  background-color: var(--bg-accent);
179
183
  }
180
184
 
181
- :not(input).error {
185
+ :is(p, span, i).error {
186
+ color: var(--fg-error);
187
+ }
188
+
189
+ input.error {
190
+ border: var(--border-error);
191
+ }
192
+
193
+ div.error {
182
194
  padding: 1em;
183
195
  border-radius: 0.5em;
184
196
  background-color: var(--bg-error);
197
+ border: var(--border-error);
185
198
  }
186
199
 
187
- .error-text {
188
- color: hsl(0 50 50%);
189
- }
190
-
191
- input.error {
192
- border: 1px solid var(--bg-error);
200
+ div.warning {
201
+ padding: 1em;
202
+ border-radius: 0.5em;
203
+ background-color: var(--bg-warning);
204
+ border: var(--border-warning);
193
205
  }
194
206
 
195
- .success {
207
+ div.success {
196
208
  padding: 1em;
197
209
  border-radius: 0.5em;
198
210
  background-color: var(--bg-success);
211
+ border: var(--border-success);
199
212
  }
200
213
 
201
214
  .subtle {
@@ -215,6 +228,7 @@ input.error {
215
228
  border: var(--border-error);
216
229
  background-color: hsl(0 20 calc(var(--bg-light) + var(--light-step)));
217
230
  color: hsl(0 33 var(--fg-light));
231
+ --fill: hsl(0 33 var(--fg-light));
218
232
  accent-color: hsl(0 33 var(--fg-light));
219
233
  }
220
234
 
@@ -308,6 +322,36 @@ h6 {
308
322
  }
309
323
  }
310
324
 
325
+ #toasts {
326
+ position: absolute;
327
+ right: 1em;
328
+ top: 1em;
329
+ width: 0;
330
+ height: 0;
331
+ overflow: visible;
332
+ display: flex;
333
+ flex-direction: column;
334
+ gap: 1em;
335
+ text-align: right;
336
+ align-items: end;
337
+ }
338
+
339
+ div.toast {
340
+ width: max-content;
341
+ padding: 0.4em 0.8em;
342
+ border-radius: 0.5em;
343
+
344
+ &:hover {
345
+ animation-play-state: paused;
346
+ }
347
+
348
+ &.plain,
349
+ &.info {
350
+ border: var(--border-accent);
351
+ background-color: var(--bg-accent);
352
+ }
353
+ }
354
+
311
355
  @media (width >= 700px) {
312
356
  .mobile-only {
313
357
  display: none;
@@ -333,7 +377,7 @@ h6 {
333
377
  }
334
378
 
335
379
  .mobile-button:hover {
336
- background-color: hsl(var(--hue) 15 calc(var(--bg-light) + (var(--light-step) * 2)));
380
+ background-color: var(--bg-accent);
337
381
  }
338
382
 
339
383
  .mobile-float-left {
package/assets/theme.css CHANGED
@@ -35,10 +35,20 @@ html {
35
35
  --bg-accent: hsl(var(--hue) 15 calc(var(--bg-light) + (var(--light-step) * 2)));
36
36
  --bg-alt: hsl(var(--hue) 5 calc(var(--bg-light) + var(--light-step)));
37
37
  --bg-strong: hsl(var(--hue) 20 calc(var(--bg-light) + var(--light-step)));
38
+
38
39
  --bg-error: hsl(0 40 var(--bg-light-status));
40
+ --bg-warning: hsl(60 40 var(--bg-light-status));
39
41
  --bg-success: hsl(120 40 var(--bg-light-status));
42
+
40
43
  --fg-accent: hsl(var(--hue) 15 var(--fg-light));
41
44
  --fg-strong: hsl(var(--hue) 25 calc(var(--fg-light) - var(--light-step)));
45
+
46
+ --fg-error: hsl(0 50 calc(var(--fg-light) - var(--light-step)));
47
+ --fg-warning: hsl(60 50 calc(var(--fg-light) - var(--light-step)));
48
+
42
49
  --border-accent: 1px solid hsl(var(--hue) 10 calc(var(--bg-light) + (var(--light-step) * 3)));
50
+
43
51
  --border-error: 1px solid hsl(0 50 var(--fg-light));
52
+ --border-warning: 1px solid hsl(60 50 var(--fg-light));
53
+ --border-success: 1px solid hsl(120 50 var(--fg-light));
44
54
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Returns whether an element currently has an animation.
3
+ * This will be empty or `none` when prefers-reduced-motion is enabled
4
+ */
5
+ export declare function hasAnimation(element: HTMLElement): boolean;
6
+ export declare function onAnimationEnd(element: HTMLElement): Promise<void>;
7
+ /** Waits for an animation to complete on an element, after any other animations. */
8
+ export declare function animate(element: HTMLElement, animation: string): Promise<void>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Returns whether an element currently has an animation.
3
+ * This will be empty or `none` when prefers-reduced-motion is enabled
4
+ */
5
+ export function hasAnimation(element) {
6
+ const { animationName } = getComputedStyle(element);
7
+ return !!animationName && animationName !== 'none';
8
+ }
9
+ const pending = new WeakMap();
10
+ export function onAnimationEnd(element) {
11
+ if (!hasAnimation(element))
12
+ return Promise.resolve();
13
+ const { promise, resolve } = Promise.withResolvers();
14
+ element.addEventListener('animationend', () => resolve(), { once: true });
15
+ element.addEventListener('animationcancel', () => resolve(), { once: true });
16
+ return promise;
17
+ }
18
+ /** Waits for an animation to complete on an element, after any other animations. */
19
+ export async function animate(element, animation) {
20
+ await pending.get(element);
21
+ element.style.animation = animation;
22
+ const ended = onAnimationEnd(element);
23
+ pending.set(element, ended);
24
+ await ended;
25
+ if (pending.get(element) === ended)
26
+ pending.delete(element);
27
+ }
@@ -0,0 +1,2 @@
1
+ export * from './animate.js';
2
+ export * from './clipboard.js';
@@ -0,0 +1,2 @@
1
+ export * from './animate.js';
2
+ export * from './clipboard.js';
package/dist/locales.d.ts CHANGED
@@ -10,6 +10,8 @@ declare let currentLoaded: {
10
10
  readonly named_title: "Permissions for <strong>{name}</strong>";
11
11
  readonly owner: "Owner";
12
12
  readonly title: "Permissions";
13
+ readonly remove: "Remove";
14
+ readonly toast_removed: "Removed access";
13
15
  };
14
16
  readonly AppMenu: {
15
17
  readonly failed: "Couldn't load apps.";
@@ -78,6 +80,7 @@ declare let currentLoaded: {
78
80
  readonly preferences: "Preferences";
79
81
  readonly register: "Register";
80
82
  readonly sessions: "Sessions";
83
+ readonly success: "Success";
81
84
  readonly unknown: "Unknown";
82
85
  readonly unnamed: "Unnamed";
83
86
  readonly username: "Name";
@@ -1,8 +1,9 @@
1
1
  <script lang="ts">
2
- import { addToACL, getACL, text, updateACL, userInfo } from '@axium/client';
2
+ import { addToACL, getACL, removeFromACL, text, updateACL, userInfo } from '@axium/client';
3
3
  import type { AccessControllable, AccessTarget, User } from '@axium/core';
4
4
  import { getTarget, pickPermissions } from '@axium/core';
5
5
  import { errorText } from '@axium/core/io';
6
+ import { toastStatus } from './toast.js';
6
7
  import type { HTMLDialogAttributes } from 'svelte/elements';
7
8
  import Icon from './Icon.svelte';
8
9
  import UserCard from './UserCard.svelte';
@@ -27,7 +28,7 @@
27
28
  }
28
29
  </script>
29
30
 
30
- <dialog bind:this={dialog} {...rest}>
31
+ <dialog bind:this={dialog} {...rest} onclick={e => e.stopPropagation()}>
31
32
  {#if item.name}
32
33
  <h3>{@html text('component.AccessControlDialog.named_title', { $html: true, name: item.name })}</h3>
33
34
  {:else}
@@ -38,16 +39,16 @@
38
39
  <div class="error">{error}</div>
39
40
  {/if}
40
41
 
41
- <div class="AccessControl">
42
+ <div class="AccessControl text">
42
43
  {#if item.user}
43
44
  <UserCard user={item.user} />
44
45
  {:else if item}
45
- {#await userInfo(item.userId) then user}<UserCard {user} />{/await}
46
+ {#await userInfo(item.userId) then user}<UserCard {user} />{:catch}<i>{text('generic.unknown')}</i>{/await}
46
47
  {/if}
47
48
  <span>{text('component.AccessControlDialog.owner')}</span>
48
49
  </div>
49
50
 
50
- {#each acl as control}
51
+ {#each acl as control, i (control.userId || control.role || control.tag)}
51
52
  {@const update = (key: string) => async (e: Event & { currentTarget: HTMLInputElement }) => {
52
53
  try {
53
54
  const updated = await updateACL(itemType, item.id, getTarget(control), { [key]: e.currentTarget.checked });
@@ -57,16 +58,36 @@
57
58
  }
58
59
  }}
59
60
  <div class="AccessControl">
60
- {#if control.user}
61
- <UserCard user={control.user} />
62
- {:else if control.role}
63
- <span class="icon-text">
64
- <Icon i="at" />
65
- <span>{control.role}</span>
66
- </span>
67
- {:else}
68
- <i>{text('generic.unknown')}</i>
69
- {/if}
61
+ <div class="target">
62
+ {#if control.user}
63
+ <UserCard user={control.user} />
64
+ {:else if control.role}
65
+ <span class="icon-text">
66
+ <Icon i="at" />
67
+ <span>{control.role}</span>
68
+ </span>
69
+ {:else if control.tag}
70
+ <span class="icon-text">
71
+ <Icon i="hashtag" />
72
+ <span>{control.tag}</span>
73
+ </span>
74
+ {:else}
75
+ <i>{text('generic.unknown')}</i>
76
+ {/if}
77
+ {#if editable}
78
+ <button
79
+ class="icon-text danger"
80
+ onclick={() =>
81
+ toastStatus(
82
+ removeFromACL(itemType, item.id, getTarget(control)).then(() => acl.splice(i, 1)),
83
+ text('component.AccessControlDialog.toast_removed')
84
+ )}
85
+ >
86
+ <Icon i="user-minus" />
87
+ <span>{text('component.AccessControlDialog.remove')}</span>
88
+ </button>
89
+ {/if}
90
+ </div>
70
91
  <div class="permissions">
71
92
  {#each Object.entries(pickPermissions(control) as Record<string, boolean>) as [key, value]}
72
93
  {@const id = `${item.id}.${getTarget(control)}.${key}`}
@@ -79,7 +100,7 @@
79
100
  {:else}
80
101
  <Icon i={value ? 'check' : 'xmark'} />
81
102
  {/if}
82
- <span>{key}</span>
103
+ <span>{text(`permission.${itemType}.${key}`, { $default: key })}</span>
83
104
  </span>
84
105
  {/each}
85
106
  </div>
@@ -113,12 +134,23 @@
113
134
  .AccessControl {
114
135
  display: grid;
115
136
  gap: 1em;
116
- grid-template-columns: 1fr 10em;
117
- min-width: 30em;
137
+ grid-template-columns: 1fr 1fr;
118
138
  padding: 1em 2em;
139
+ border-bottom: var(--border-accent);
140
+
141
+ &.text {
142
+ align-items: center;
143
+ }
144
+
145
+ .target {
146
+ display: flex;
147
+ flex-direction: column;
148
+ justify-content: space-around;
149
+ gap: 1em;
150
+ }
119
151
 
120
- &:not(.public) {
121
- border-bottom: var(--border-accent);
152
+ @media (width > 700px) {
153
+ min-width: 30em;
122
154
  }
123
155
  }
124
156
  </style>
@@ -2,7 +2,7 @@
2
2
  import { fade } from 'svelte/transition';
3
3
  import { wait } from 'utilium';
4
4
  import Icon from './Icon.svelte';
5
- import * as clip from '@axium/client/clipboard';
5
+ import * as clip from '@axium/client/gui';
6
6
 
7
7
  const { value, type = 'text/plain' }: { value: BlobPart; type?: string } = $props();
8
8
 
@@ -62,7 +62,7 @@
62
62
  <button type="submit" class={['submit', submitDanger && 'danger']}>{submitText}</button>
63
63
  {/snippet}
64
64
 
65
- <dialog bind:this={dialog} {onclose} {...rest}>
65
+ <dialog bind:this={dialog} {onclose} {...rest} onclick={e => e.stopPropagation()}>
66
66
  {@render header?.()}
67
67
  <form {onsubmit} class="main" method="dialog">
68
68
  {#if error}
@@ -67,15 +67,11 @@
67
67
  <span><img src={getUserImage(result.value)} alt={result.value.name} />{result.value.name}</span>
68
68
  {:else if result.type == 'role'}
69
69
  <span>
70
- <span class="icon-text tag-or-role" style:background-color={colorHashRGB(result.value)}
71
- ><Icon i="at" />{result.value}</span
72
- >
70
+ <span class="icon-text non-user"><Icon i="at" />{result.value}</span>
73
71
  </span>
74
72
  {:else if result.type == 'tag'}
75
73
  <span>
76
- <span class="icon-text tag-or-role" style:background-color={colorHashRGB(result.value)}
77
- ><Icon i="hashtag" />{result.value}</span
78
- >
74
+ <span class="icon-text non-user"><Icon i="hashtag" />{result.value}</span>
79
75
  </span>
80
76
  {:else if result.type == 'exact'}
81
77
  <span class="non-user">{result.value}</span>
@@ -90,7 +90,7 @@
90
90
  {#snippet _in(rest: HTMLInputAttributes)}
91
91
  <div class="ZodInput">
92
92
  {#if !noLabel}<label for={id}>{labelText}</label>{/if}
93
- {#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
93
+ {#if error}<span class="ZodInput-error">{error}</span>{/if}
94
94
  <input {id} {...rest} bind:value {onchange} {oninput} required={!optional} {defaultValue} class={[error && 'error']} />
95
95
  </div>
96
96
  {/snippet}
@@ -136,7 +136,7 @@
136
136
  {:else if schema.type == 'array'}
137
137
  <div class="ZodInput">
138
138
  {#if !noLabel}<label for={id}>{labelText}</label>{/if}
139
- {#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
139
+ {#if error}<span class="ZodInput-error">{error}</span>{/if}
140
140
  <div class="ZodInput-array">
141
141
  {#each value, i}
142
142
  <div class="ZodInput-element">
@@ -180,7 +180,7 @@
180
180
  {:else if schema.type == 'enum'}
181
181
  <div class="ZodInput">
182
182
  {#if !noLabel}<label for={id}>{labelText}</label>{/if}
183
- {#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
183
+ {#if error}<span class="ZodInput-error">{error}</span>{/if}
184
184
  <select {id} {onchange} bind:value required={!optional}>
185
185
  {#each Object.entries(schema.enum) as [key, value]}
186
186
  <option {value} selected={value === value}>{key}</option>
@@ -189,7 +189,7 @@
189
189
  </div>
190
190
  {:else}
191
191
  <!-- No idea how to render this -->
192
- <i class="error-text">{text('component.ZodInput.invalid_type', { type: JSON.stringify((schema as ZodPref)?.def?.type) })}</i>
192
+ <i class="error">{text('component.ZodInput.invalid_type', { type: JSON.stringify((schema as ZodPref)?.def?.type) })}</i>
193
193
  {/if}
194
194
 
195
195
  <style>
@@ -198,6 +198,7 @@
198
198
  position-anchor: --zod-input;
199
199
  bottom: calc(anchor(top) - 0.3em);
200
200
  left: anchor(left);
201
+ color: var(--fg-error);
201
202
  }
202
203
 
203
204
  .ZodInput {
@@ -49,6 +49,10 @@ export function contextMenu(...menuItems: (ContextMenuItem | false | null | unde
49
49
  let _forcePopover = false;
50
50
 
51
51
  element.oncontextmenu = (e: MouseEvent) => {
52
+ for (let node = e.target as HTMLElement | null; node && node !== element; node = node.parentElement) {
53
+ if (node instanceof HTMLDialogElement || (node.popover && node !== menu)) return;
54
+ }
55
+
52
56
  e.preventDefault();
53
57
  e.stopPropagation();
54
58
 
package/lib/index.ts CHANGED
@@ -11,7 +11,6 @@ export { default as Popover } from './Popover.svelte';
11
11
  export { default as Register } from './Register.svelte';
12
12
  export { default as SessionList } from './SessionList.svelte';
13
13
  export { default as SidebarLayout } from './SidebarLayout.svelte';
14
- export { default as Toast } from './Toast.svelte';
15
14
  export { default as Upload } from './Upload.svelte';
16
15
  export { default as URLText } from './URLText.svelte';
17
16
  export { default as UserCard } from './UserCard.svelte';
package/lib/toast.ts ADDED
@@ -0,0 +1,81 @@
1
+ import { debug, errorText } from '@axium/core/io';
2
+ import { text } from '@axium/client/locales';
3
+ import { animate } from '@axium/client/gui';
4
+ import Icon from './Icon.svelte';
5
+ import { mount } from 'svelte';
6
+
7
+ const list = document.querySelector<HTMLDivElement>('#toasts')!;
8
+
9
+ const toastIcons = {
10
+ success: 'check',
11
+ warning: 'regular/triangle-exclamation',
12
+ error: 'octagon-xmark',
13
+ info: 'regular/circle-info',
14
+ };
15
+
16
+ /** Used to determine icon and styling */
17
+ export type ToastType = 'plain' | keyof typeof toastIcons;
18
+
19
+ const durationMultiplier = {
20
+ warning: 1.25,
21
+ error: 1.5,
22
+ } as Record<ToastType, number>;
23
+
24
+ export async function toast(type: ToastType, message: any): Promise<void> {
25
+ const text = errorText(message);
26
+
27
+ const toast = document.createElement('div');
28
+ toast.classList.add('toast', 'icon-text', type);
29
+ if (type != 'plain') mount(Icon, { target: toast, props: { i: toastIcons[type] } });
30
+
31
+ const span = document.createElement('span');
32
+ span.textContent = text;
33
+ toast.appendChild(span);
34
+
35
+ list.appendChild(toast);
36
+
37
+ async function dismiss() {
38
+ debug('Toast dismissed');
39
+ await animate(toast, 'var(--A-slide-out-right)');
40
+ toast.remove();
41
+ }
42
+
43
+ let persisted = false;
44
+
45
+ function persist() {
46
+ debug('Toast persisted');
47
+ persisted = true;
48
+ toast.onclick = null;
49
+ toast.style.animation = 'none';
50
+ toast.style.opacity = '1';
51
+
52
+ const button = document.createElement('button');
53
+ button.classList.add('reset');
54
+ button.onclick = dismiss;
55
+ mount(Icon, { target: button, props: { i: 'xmark-large' } });
56
+ toast.appendChild(button);
57
+ }
58
+
59
+ if (message && message instanceof Error) return persist();
60
+
61
+ /**
62
+ * @see https://ux.stackexchange.com/a/85898
63
+ */
64
+ const duration = Math.min(Math.max(text.length * 50 * (durationMultiplier[type] || 1), 2000), 7000);
65
+
66
+ toast.onclick = persist;
67
+ await animate(toast, `fade-subtle ${duration}ms ease reverse forwards`);
68
+ if (!persisted) await dismiss();
69
+ else debug('Toast not auto-dismissed due to persistence');
70
+ }
71
+
72
+ export async function toastStatus(promise: Promise<unknown>, successMessage: string = text('generic.success')): Promise<void> {
73
+ try {
74
+ await promise;
75
+ await toast('success', successMessage);
76
+ } catch (err) {
77
+ await toast('error', err);
78
+ }
79
+ }
80
+
81
+ Object.assign(globalThis, { toast, toastStatus });
package/locales/en.json CHANGED
@@ -3,7 +3,9 @@
3
3
  "AccessControlDialog": {
4
4
  "named_title": "Permissions for <strong>{name}</strong>",
5
5
  "owner": "Owner",
6
- "title": "Permissions"
6
+ "title": "Permissions",
7
+ "remove": "Remove",
8
+ "toast_removed": "Removed access"
7
9
  },
8
10
  "AppMenu": {
9
11
  "failed": "Couldn't load apps.",
@@ -72,6 +74,7 @@
72
74
  "preferences": "Preferences",
73
75
  "register": "Register",
74
76
  "sessions": "Sessions",
77
+ "success": "Success",
75
78
  "unknown": "Unknown",
76
79
  "unnamed": "Unnamed",
77
80
  "username": "Name",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/client",
3
- "version": "0.17.3",
3
+ "version": "0.18.1",
4
4
  "author": "James Prevett <jp@jamespre.dev>",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -33,6 +33,7 @@
33
33
  "exports": {
34
34
  "./package.json": "./package.json",
35
35
  ".": "./dist/index.js",
36
+ "./gui": "./dist/gui/index.js",
36
37
  "./*": "./dist/*.js",
37
38
  "./components": "./lib/index.js",
38
39
  "./components/*": "./lib/*.svelte",
package/lib/Toast.svelte DELETED
@@ -1,35 +0,0 @@
1
- <script lang="ts">
2
- import { fade } from 'svelte/transition';
3
-
4
- const { enabled, children, delay = 5000, duration = 1000, ...rest } = $props();
5
-
6
- let hiding = $state(false);
7
-
8
- const show = $derived(enabled && !hiding);
9
- </script>
10
-
11
- {#if show}
12
- <div
13
- class="Toast"
14
- in:fade|global={{ duration }}
15
- onintroend={() => (hiding = true)}
16
- out:fade|global={{ delay, duration }}
17
- onoutroend={() => (hiding = false)}
18
- {...rest}
19
- >
20
- {@render children()}
21
- </div>
22
- {/if}
23
-
24
- <style>
25
- .Toast {
26
- position: fixed;
27
- bottom: 1em;
28
- left: calc(50% - 10em);
29
- right: calc(50% - 10em);
30
- width: 20em;
31
- padding: 0.5em 1em;
32
- border-radius: 1em;
33
- opacity: 0.5;
34
- }
35
- </style>
File without changes
File without changes