@joewinke/jatui 0.42.0 → 0.43.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.
- package/CLAUDE.md +23 -3
- package/THIRD_PARTY_NOTICES.md +14 -0
- package/dist/actions/dismissOutside.d.ts +52 -0
- package/dist/actions/dismissOutside.js +80 -0
- package/dist/components/FileDropzone.svelte +165 -3
- package/dist/components/FileDropzone.svelte.d.ts +9 -0
- package/dist/components/TextInput.svelte +71 -20
- package/dist/components/TextInput.svelte.d.ts +7 -0
- package/dist/components/ThumbnailGrid.svelte +106 -1
- package/dist/components/ThumbnailGrid.svelte.d.ts +7 -0
- package/dist/components/basic/Alert.svelte +113 -16
- package/dist/components/basic/Alert.svelte.d.ts +17 -1
- package/dist/components/basic/Checkbox.svelte +53 -1
- package/dist/components/basic/Checkbox.svelte.d.ts +6 -0
- package/dist/components/basic/RadioGroup.svelte +59 -3
- package/dist/components/basic/RadioGroup.svelte.d.ts +15 -0
- package/dist/components/basic/Slider.svelte +257 -9
- package/dist/components/basic/Slider.svelte.d.ts +14 -2
- package/dist/components/basic/sliderMath.d.ts +29 -0
- package/dist/components/basic/sliderMath.js +64 -0
- package/dist/components/field/FieldHint.svelte +29 -0
- package/dist/components/field/FieldHint.svelte.d.ts +17 -0
- package/dist/components/field/FieldLabel.svelte +100 -0
- package/dist/components/field/FieldLabel.svelte.d.ts +29 -0
- package/dist/components/form-cards/CheckGlyph.svelte +138 -0
- package/dist/components/form-cards/CheckGlyph.svelte.d.ts +29 -0
- package/dist/components/form-cards/CheckboxCard.svelte +120 -0
- package/dist/components/form-cards/CheckboxCard.svelte.d.ts +33 -0
- package/dist/components/form-cards/ChoiceCard.svelte +156 -0
- package/dist/components/form-cards/ChoiceCard.svelte.d.ts +42 -0
- package/dist/components/form-cards/SwitchCard.svelte +114 -0
- package/dist/components/form-cards/SwitchCard.svelte.d.ts +28 -0
- package/dist/components/toast/ToastHost.svelte +56 -3
- package/dist/components/toast/ToastHost.svelte.d.ts +9 -1
- package/dist/components/toast/ToastItem.svelte +75 -23
- package/dist/components/toast/ToastItem.svelte.d.ts +2 -0
- package/dist/components/uploadProgress.d.ts +30 -0
- package/dist/components/uploadProgress.js +47 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +11 -1
- package/dist/stores/toast.d.ts +35 -0
- package/dist/stores/toast.js +15 -1
- package/dist/styles/motion.css +72 -0
- package/dist/styles/rail.css +51 -0
- package/package.json +1 -1
|
@@ -1,21 +1,74 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type ToastPosition =
|
|
3
|
+
| 'bottom-end'
|
|
4
|
+
| 'bottom-start'
|
|
5
|
+
| 'bottom-center'
|
|
6
|
+
| 'top-end'
|
|
7
|
+
| 'top-start'
|
|
8
|
+
| 'top-center';
|
|
9
|
+
</script>
|
|
10
|
+
|
|
1
11
|
<script lang="ts">
|
|
2
12
|
import { getToasts } from '../../stores/toast.js';
|
|
3
13
|
import ToastItem from './ToastItem.svelte';
|
|
4
14
|
|
|
15
|
+
interface Props {
|
|
16
|
+
/**
|
|
17
|
+
* Viewport corner/edge. Logical: `end` is the right edge in LTR and the
|
|
18
|
+
* left edge in RTL. Default 'bottom-end' (the original bottom-right).
|
|
19
|
+
*/
|
|
20
|
+
position?: ToastPosition;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let { position = 'bottom-end' }: Props = $props();
|
|
24
|
+
|
|
5
25
|
const toasts = $derived(getToasts());
|
|
26
|
+
const edge = $derived<'top' | 'bottom'>(position.startsWith('top') ? 'top' : 'bottom');
|
|
6
27
|
</script>
|
|
7
28
|
|
|
8
29
|
<!--
|
|
9
30
|
Mount once in your app root (e.g. +layout.svelte).
|
|
10
|
-
Fixed
|
|
31
|
+
Fixed to a viewport corner, pointer-events only on the toast list.
|
|
32
|
+
Newest toast sits on top of the stack; at top positions that puts it
|
|
33
|
+
nearest the edge, so the stack reads newest-first downward.
|
|
11
34
|
-->
|
|
12
35
|
<div
|
|
13
36
|
aria-label="Notifications"
|
|
14
|
-
class="toast-host pointer-events-none fixed
|
|
37
|
+
class="toast-host toast-host--{position} pointer-events-none fixed z-[9999] flex flex-col-reverse gap-2 w-80 max-w-[calc(100vw-2rem)]"
|
|
38
|
+
data-position={position}
|
|
15
39
|
>
|
|
16
40
|
<ul class="contents">
|
|
17
41
|
{#each toasts as toast, i (toast.id)}
|
|
18
|
-
<ToastItem {toast} index={i} total={toasts.length} />
|
|
42
|
+
<ToastItem {toast} index={i} total={toasts.length} {edge} />
|
|
19
43
|
{/each}
|
|
20
44
|
</ul>
|
|
21
45
|
</div>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.toast-host--bottom-end,
|
|
49
|
+
.toast-host--bottom-start,
|
|
50
|
+
.toast-host--bottom-center {
|
|
51
|
+
bottom: 1rem;
|
|
52
|
+
}
|
|
53
|
+
.toast-host--top-end,
|
|
54
|
+
.toast-host--top-start,
|
|
55
|
+
.toast-host--top-center {
|
|
56
|
+
top: 1rem;
|
|
57
|
+
}
|
|
58
|
+
.toast-host--bottom-end,
|
|
59
|
+
.toast-host--top-end {
|
|
60
|
+
inset-inline-end: 1rem;
|
|
61
|
+
align-items: flex-end;
|
|
62
|
+
}
|
|
63
|
+
.toast-host--bottom-start,
|
|
64
|
+
.toast-host--top-start {
|
|
65
|
+
inset-inline-start: 1rem;
|
|
66
|
+
align-items: flex-start;
|
|
67
|
+
}
|
|
68
|
+
.toast-host--bottom-center,
|
|
69
|
+
.toast-host--top-center {
|
|
70
|
+
inset-inline: 0;
|
|
71
|
+
margin-inline: auto;
|
|
72
|
+
align-items: center;
|
|
73
|
+
}
|
|
74
|
+
</style>
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
export type ToastPosition = 'bottom-end' | 'bottom-start' | 'bottom-center' | 'top-end' | 'top-start' | 'top-center';
|
|
2
|
+
interface Props {
|
|
3
|
+
/**
|
|
4
|
+
* Viewport corner/edge. Logical: `end` is the right edge in LTR and the
|
|
5
|
+
* left edge in RTL. Default 'bottom-end' (the original bottom-right).
|
|
6
|
+
*/
|
|
7
|
+
position?: ToastPosition;
|
|
8
|
+
}
|
|
9
|
+
declare const ToastHost: import("svelte").Component<Props, {}, "">;
|
|
2
10
|
type ToastHost = ReturnType<typeof ToastHost>;
|
|
3
11
|
export default ToastHost;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { dismissToast, type Toast } from '../../stores/toast.js';
|
|
3
|
+
import CloseButton from '../CloseButton.svelte';
|
|
3
4
|
|
|
4
5
|
type Props = {
|
|
5
6
|
toast: Toast;
|
|
6
7
|
index: number;
|
|
7
8
|
total: number;
|
|
9
|
+
/** Which viewport edge the host is pinned to — sets the entrance direction. */
|
|
10
|
+
edge?: 'top' | 'bottom';
|
|
8
11
|
};
|
|
9
12
|
|
|
10
|
-
let { toast, index, total }: Props = $props();
|
|
13
|
+
let { toast, index, total, edge = 'bottom' }: Props = $props();
|
|
14
|
+
const enterSign = $derived(edge === 'top' ? -1 : 1);
|
|
11
15
|
|
|
12
16
|
// Stack compression: older toasts (lower index) scale down and fade behind newer ones
|
|
13
17
|
// index 0 = oldest (back), index total-1 = newest (front)
|
|
@@ -172,10 +176,10 @@ const detailsId = $derived(`toast-details-${toast.id}`);
|
|
|
172
176
|
isDragging
|
|
173
177
|
? `translateX(${dragX}px) rotate(${dragX * 0.02}deg)`
|
|
174
178
|
: exiting
|
|
175
|
-
? `translateY(
|
|
179
|
+
? `translateY(${20 * enterSign}px) scale(0.9)`
|
|
176
180
|
: mounted
|
|
177
181
|
? `translateX(0) rotate(0deg)`
|
|
178
|
-
: `translateY(
|
|
182
|
+
: `translateY(${40 * enterSign}px) scale(0.92)`
|
|
179
183
|
);
|
|
180
184
|
|
|
181
185
|
const itemOpacity = $derived(
|
|
@@ -192,13 +196,25 @@ const detailsId = $derived(`toast-details-${toast.id}`);
|
|
|
192
196
|
isDragging
|
|
193
197
|
? 'none'
|
|
194
198
|
: exiting
|
|
195
|
-
? 'transform var(--motion-enter) var(--motion-glide), opacity var(--motion-enter) var(--motion-glide)'
|
|
196
|
-
: 'transform var(--motion-enter) var(--motion-spring), opacity var(--motion-fast) var(--motion-glide)'
|
|
199
|
+
? 'transform var(--motion-enter) var(--motion-glide), opacity var(--motion-enter) var(--motion-glide), filter var(--motion-enter) var(--motion-glide)'
|
|
200
|
+
: 'transform var(--motion-enter) var(--motion-spring), opacity var(--motion-fast) var(--motion-glide), filter var(--motion-enter) var(--motion-glide)'
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Blur on enter/exit (BoardUI notification idea). The class only marks the
|
|
204
|
+
// hidden state; CSS drops the blur under data-motion="calm" and reduced motion.
|
|
205
|
+
const veiled = $derived(!isDragging && (exiting || !mounted));
|
|
206
|
+
|
|
207
|
+
const titleId = $derived(`toast-title-${toast.id}`);
|
|
208
|
+
// Legacy toasts from an older store shape may lack the normalised list.
|
|
209
|
+
const actionList = $derived(
|
|
210
|
+
toast.actions ?? (toast.action ? [{ ...toast.action, variant: 'ghost' as const }] : [])
|
|
197
211
|
);
|
|
198
212
|
</script>
|
|
199
213
|
|
|
200
214
|
<li
|
|
201
215
|
class="toast-item pointer-events-auto w-full"
|
|
216
|
+
class:toast-veiled={veiled}
|
|
217
|
+
aria-labelledby={toast.title ? titleId : undefined}
|
|
202
218
|
style:transform={itemTransform}
|
|
203
219
|
style:opacity={itemOpacity}
|
|
204
220
|
style:transition={dragTransition}
|
|
@@ -219,6 +235,27 @@ const detailsId = $derived(`toast-details-${toast.id}`);
|
|
|
219
235
|
style:transition="transform var(--motion-move) var(--motion-glide), opacity var(--motion-move) var(--motion-glide)"
|
|
220
236
|
>
|
|
221
237
|
<!-- Undo countdown bar -->
|
|
238
|
+
{#if toast.showProgress && !toast.onUndo}
|
|
239
|
+
<span
|
|
240
|
+
class="toast-progress absolute bottom-0 left-0 h-0.5 w-full origin-left bg-current opacity-40"
|
|
241
|
+
style:animation-duration="{toast.durationMs}ms"
|
|
242
|
+
style:animation-play-state={paused ? 'paused' : 'running'}
|
|
243
|
+
aria-hidden="true"
|
|
244
|
+
></span>
|
|
245
|
+
{/if}
|
|
246
|
+
|
|
247
|
+
{#if toast.avatar}
|
|
248
|
+
<img
|
|
249
|
+
src={toast.avatar.src}
|
|
250
|
+
alt={toast.avatar.alt}
|
|
251
|
+
class="toast-avatar size-8 shrink-0 self-start rounded-full object-cover"
|
|
252
|
+
/>
|
|
253
|
+
{:else if toast.icon}
|
|
254
|
+
<span class="toast-icon flex size-5 shrink-0 items-center justify-center self-start" aria-hidden="true">
|
|
255
|
+
{#if typeof toast.icon === 'string'}{toast.icon}{:else}{@render toast.icon()}{/if}
|
|
256
|
+
</span>
|
|
257
|
+
{/if}
|
|
258
|
+
|
|
222
259
|
{#if toast.onUndo && !undoDone}
|
|
223
260
|
<span
|
|
224
261
|
class="toast-progress absolute bottom-0 left-0 h-0.5 w-full origin-left bg-current opacity-40"
|
|
@@ -228,7 +265,10 @@ const detailsId = $derived(`toast-details-${toast.id}`);
|
|
|
228
265
|
{/if}
|
|
229
266
|
|
|
230
267
|
<div class="flex-1 min-w-0">
|
|
231
|
-
|
|
268
|
+
{#if toast.title}
|
|
269
|
+
<p id={titleId} class="toast-title text-sm font-semibold">{toast.title}</p>
|
|
270
|
+
{/if}
|
|
271
|
+
<span class="text-sm" class:opacity-80={!!toast.title}>{toast.message}</span>
|
|
232
272
|
|
|
233
273
|
{#if toast.details}
|
|
234
274
|
<button
|
|
@@ -270,26 +310,26 @@ const detailsId = $derived(`toast-details-${toast.id}`);
|
|
|
270
310
|
>
|
|
271
311
|
Undo
|
|
272
312
|
</button>
|
|
273
|
-
{:else if
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
313
|
+
{:else if actionList.length > 0}
|
|
314
|
+
<div class="toast-actions flex shrink-0 flex-wrap items-center gap-1">
|
|
315
|
+
{#each actionList as act, ai (ai)}
|
|
316
|
+
<button
|
|
317
|
+
type="button"
|
|
318
|
+
class="btn btn-xs font-semibold {act.variant === 'primary' ? 'btn-primary' : 'btn-ghost'}"
|
|
319
|
+
onclick={() => { act.onClick(); triggerDismiss(); }}
|
|
320
|
+
>
|
|
321
|
+
{act.label}
|
|
322
|
+
</button>
|
|
323
|
+
{/each}
|
|
324
|
+
</div>
|
|
281
325
|
{/if}
|
|
282
326
|
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
327
|
+
<CloseButton
|
|
328
|
+
label="Dismiss notification"
|
|
329
|
+
size="xs"
|
|
330
|
+
shape="square"
|
|
287
331
|
onclick={triggerDismiss}
|
|
288
|
-
|
|
289
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
290
|
-
<path d="M18 6L6 18M6 6l12 12"/>
|
|
291
|
-
</svg>
|
|
292
|
-
</button>
|
|
332
|
+
/>
|
|
293
333
|
</div>
|
|
294
334
|
</li>
|
|
295
335
|
|
|
@@ -300,6 +340,14 @@ const detailsId = $derived(`toast-details-${toast.id}`);
|
|
|
300
340
|
user-select: none;
|
|
301
341
|
}
|
|
302
342
|
|
|
343
|
+
.toast-item.toast-veiled {
|
|
344
|
+
filter: blur(4px);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
:global([data-motion='calm']) .toast-item.toast-veiled {
|
|
348
|
+
filter: none;
|
|
349
|
+
}
|
|
350
|
+
|
|
303
351
|
.toast-progress {
|
|
304
352
|
animation-name: drain;
|
|
305
353
|
animation-timing-function: linear;
|
|
@@ -328,5 +376,9 @@ const detailsId = $derived(`toast-details-${toast.id}`);
|
|
|
328
376
|
.toast-item > div {
|
|
329
377
|
transition: none !important;
|
|
330
378
|
}
|
|
379
|
+
|
|
380
|
+
.toast-item.toast-veiled {
|
|
381
|
+
filter: none;
|
|
382
|
+
}
|
|
331
383
|
}
|
|
332
384
|
</style>
|
|
@@ -3,6 +3,8 @@ type Props = {
|
|
|
3
3
|
toast: Toast;
|
|
4
4
|
index: number;
|
|
5
5
|
total: number;
|
|
6
|
+
/** Which viewport edge the host is pinned to — sets the entrance direction. */
|
|
7
|
+
edge?: 'top' | 'bottom';
|
|
6
8
|
};
|
|
7
9
|
declare const ToastItem: import("svelte").Component<Props, {}, "">;
|
|
8
10
|
type ToastItem = ReturnType<typeof ToastItem>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure math behind the upload-progress strokes on FileDropzone (perimeter
|
|
3
|
+
* border) and ThumbnailGrid (per-tile ring). DOM-free so it is unit-testable
|
|
4
|
+
* under node:test (tests/unit/uploadProgress.test.ts).
|
|
5
|
+
*/
|
|
6
|
+
/** Clamp a 0–100 progress value; `null`/`undefined`/NaN mean "no progress shown". */
|
|
7
|
+
export declare function clampProgress(value: number | null | undefined): number | null;
|
|
8
|
+
/** True while an upload is in flight (a progress value below 100). */
|
|
9
|
+
export declare function isInFlight(value: number | null | undefined): boolean;
|
|
10
|
+
/** Whole-number percent for the badge / aria-valuenow. Floors so 99.6 never reads "100". */
|
|
11
|
+
export declare function progressPercent(value: number | null | undefined): number;
|
|
12
|
+
/**
|
|
13
|
+
* stroke-dashoffset for a path drawn with `pathLength={length}` and
|
|
14
|
+
* `stroke-dasharray={length}`: 0 = fully drawn, `length` = empty.
|
|
15
|
+
*/
|
|
16
|
+
export declare function dashOffset(value: number | null | undefined, length?: number): number;
|
|
17
|
+
export interface PerimeterRect {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
rx: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Geometry for an SVG `<rect>` whose stroke sits exactly on a CSS border of
|
|
26
|
+
* width `stroke` around a `width`×`height` border-box with `radius` corners.
|
|
27
|
+
* The stroke is centred on the path, so the rect is inset by half a stroke and
|
|
28
|
+
* the corner radius shrinks by the same amount (never below 0).
|
|
29
|
+
*/
|
|
30
|
+
export declare function perimeterRect(width: number, height: number, stroke?: number, radius?: number): PerimeterRect;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure math behind the upload-progress strokes on FileDropzone (perimeter
|
|
3
|
+
* border) and ThumbnailGrid (per-tile ring). DOM-free so it is unit-testable
|
|
4
|
+
* under node:test (tests/unit/uploadProgress.test.ts).
|
|
5
|
+
*/
|
|
6
|
+
/** Clamp a 0–100 progress value; `null`/`undefined`/NaN mean "no progress shown". */
|
|
7
|
+
export function clampProgress(value) {
|
|
8
|
+
if (value == null || Number.isNaN(value))
|
|
9
|
+
return null;
|
|
10
|
+
return Math.min(100, Math.max(0, value));
|
|
11
|
+
}
|
|
12
|
+
/** True while an upload is in flight (a progress value below 100). */
|
|
13
|
+
export function isInFlight(value) {
|
|
14
|
+
const p = clampProgress(value);
|
|
15
|
+
return p !== null && p < 100;
|
|
16
|
+
}
|
|
17
|
+
/** Whole-number percent for the badge / aria-valuenow. Floors so 99.6 never reads "100". */
|
|
18
|
+
export function progressPercent(value) {
|
|
19
|
+
const p = clampProgress(value);
|
|
20
|
+
if (p === null)
|
|
21
|
+
return 0;
|
|
22
|
+
return p >= 100 ? 100 : Math.floor(p);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* stroke-dashoffset for a path drawn with `pathLength={length}` and
|
|
26
|
+
* `stroke-dasharray={length}`: 0 = fully drawn, `length` = empty.
|
|
27
|
+
*/
|
|
28
|
+
export function dashOffset(value, length = 100) {
|
|
29
|
+
const p = clampProgress(value) ?? 0;
|
|
30
|
+
return length - (p / 100) * length;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Geometry for an SVG `<rect>` whose stroke sits exactly on a CSS border of
|
|
34
|
+
* width `stroke` around a `width`×`height` border-box with `radius` corners.
|
|
35
|
+
* The stroke is centred on the path, so the rect is inset by half a stroke and
|
|
36
|
+
* the corner radius shrinks by the same amount (never below 0).
|
|
37
|
+
*/
|
|
38
|
+
export function perimeterRect(width, height, stroke = 2, radius = 8) {
|
|
39
|
+
const inset = stroke / 2;
|
|
40
|
+
return {
|
|
41
|
+
x: inset,
|
|
42
|
+
y: inset,
|
|
43
|
+
width: Math.max(0, width - stroke),
|
|
44
|
+
height: Math.max(0, height - stroke),
|
|
45
|
+
rx: Math.max(0, radius - inset)
|
|
46
|
+
};
|
|
47
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -252,8 +252,9 @@ export { copyText, readClipboardText } from './utils/clipboard';
|
|
|
252
252
|
export { splitText } from './utils/splitText';
|
|
253
253
|
export type { SplitBy, SplitTextOptions, SplitTextResult } from './utils/splitText';
|
|
254
254
|
export { default as ToastHost } from './components/toast/ToastHost.svelte';
|
|
255
|
-
export { showToast, dismissToast, getToasts } from './stores/toast';
|
|
256
|
-
export type { Toast, ToastType, ToastOptions, DismissHandle } from './stores/toast';
|
|
255
|
+
export { showToast, dismissToast, getToasts, normalizeToastActions } from './stores/toast';
|
|
256
|
+
export type { Toast, ToastType, ToastOptions, DismissHandle, ToastAction, ToastAvatar } from './stores/toast';
|
|
257
|
+
export type { ToastPosition } from './components/toast/ToastHost.svelte';
|
|
257
258
|
export { default as Skeleton } from './components/Skeleton.svelte';
|
|
258
259
|
export { default as ProgressBar } from './components/progress/ProgressBar.svelte';
|
|
259
260
|
export { default as ProgressRing } from './components/progress/ProgressRing.svelte';
|
|
@@ -302,6 +303,15 @@ export { default as SectionNav } from './components/basic/SectionNav.svelte';
|
|
|
302
303
|
export { computeActiveSection, DEFAULT_ACTIVATION_RATIO } from './components/basic/sectionNavSpy';
|
|
303
304
|
export type { SectionRect, SpyViewport, SpyScrollState } from './components/basic/sectionNavSpy';
|
|
304
305
|
export { default as Slider } from './components/basic/Slider.svelte';
|
|
306
|
+
export { snapToStep, moveThumb, normalizePair, percentOf, bubbleLeft } from './components/basic/sliderMath';
|
|
307
|
+
export { default as CheckGlyph } from './components/form-cards/CheckGlyph.svelte';
|
|
308
|
+
export { default as ChoiceCard } from './components/form-cards/ChoiceCard.svelte';
|
|
309
|
+
export { default as CheckboxCard } from './components/form-cards/CheckboxCard.svelte';
|
|
310
|
+
export { default as SwitchCard } from './components/form-cards/SwitchCard.svelte';
|
|
311
|
+
export { default as FieldLabel } from './components/field/FieldLabel.svelte';
|
|
312
|
+
export { default as FieldHint } from './components/field/FieldHint.svelte';
|
|
313
|
+
export { dismissOutside, createDismissOutside, isInsidePath } from './actions/dismissOutside';
|
|
314
|
+
export type { DismissOutsideOptions, DismissOutsideController, DismissReason } from './actions/dismissOutside';
|
|
305
315
|
export { default as Pagination } from './components/basic/Pagination.svelte';
|
|
306
316
|
export { MAX_UNDO_DEPTH, createHistory, pushState, beginChange, endChange, untracked, undo, redo, clearHistory } from './history';
|
|
307
317
|
export type { History } from './history';
|
package/dist/index.js
CHANGED
|
@@ -294,7 +294,7 @@ export { copyText, readClipboardText } from './utils/clipboard';
|
|
|
294
294
|
export { splitText } from './utils/splitText';
|
|
295
295
|
// Toast system
|
|
296
296
|
export { default as ToastHost } from './components/toast/ToastHost.svelte';
|
|
297
|
-
export { showToast, dismissToast, getToasts } from './stores/toast';
|
|
297
|
+
export { showToast, dismissToast, getToasts, normalizeToastActions } from './stores/toast';
|
|
298
298
|
// Loading & progress family
|
|
299
299
|
export { default as Skeleton } from './components/Skeleton.svelte';
|
|
300
300
|
export { default as ProgressBar } from './components/progress/ProgressBar.svelte';
|
|
@@ -351,6 +351,16 @@ export { default as Separator } from './components/basic/Separator.svelte';
|
|
|
351
351
|
export { default as SectionNav } from './components/basic/SectionNav.svelte';
|
|
352
352
|
export { computeActiveSection, DEFAULT_ACTIVATION_RATIO } from './components/basic/sectionNavSpy';
|
|
353
353
|
export { default as Slider } from './components/basic/Slider.svelte';
|
|
354
|
+
export { snapToStep, moveThumb, normalizePair, percentOf, bubbleLeft } from './components/basic/sliderMath';
|
|
355
|
+
// BoardUI wave 5 — form polish (jatui-bns3w): choice cards + animated tick,
|
|
356
|
+
// shared field label/hint parts, dismissOutside.
|
|
357
|
+
export { default as CheckGlyph } from './components/form-cards/CheckGlyph.svelte';
|
|
358
|
+
export { default as ChoiceCard } from './components/form-cards/ChoiceCard.svelte';
|
|
359
|
+
export { default as CheckboxCard } from './components/form-cards/CheckboxCard.svelte';
|
|
360
|
+
export { default as SwitchCard } from './components/form-cards/SwitchCard.svelte';
|
|
361
|
+
export { default as FieldLabel } from './components/field/FieldLabel.svelte';
|
|
362
|
+
export { default as FieldHint } from './components/field/FieldHint.svelte';
|
|
363
|
+
export { dismissOutside, createDismissOutside, isInsidePath } from './actions/dismissOutside';
|
|
354
364
|
export { default as Pagination } from './components/basic/Pagination.svelte';
|
|
355
365
|
// Snapshot undo/redo history (wayflow-core) — also importable as 'jatui/history' (jatui-00jfh).
|
|
356
366
|
export { MAX_UNDO_DEPTH, createHistory, pushState, beginChange, endChange, untracked, undo, redo, clearHistory } from './history';
|
package/dist/stores/toast.d.ts
CHANGED
|
@@ -1,36 +1,71 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
export type ToastType = 'info' | 'success' | 'warning' | 'error';
|
|
3
|
+
/** One button on a toast. `variant` 'primary' renders a filled button; the
|
|
4
|
+
* default 'ghost' matches the legacy single-action button. */
|
|
5
|
+
export type ToastAction = {
|
|
6
|
+
label: string;
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
variant?: 'primary' | 'ghost';
|
|
9
|
+
};
|
|
10
|
+
/** Image shown in place of the status icon (e.g. the sender of a message). */
|
|
11
|
+
export type ToastAvatar = {
|
|
12
|
+
src: string;
|
|
13
|
+
alt: string;
|
|
14
|
+
};
|
|
2
15
|
export type ToastOptions = {
|
|
3
16
|
type?: ToastType;
|
|
4
17
|
message: string;
|
|
18
|
+
/** Bold heading above `message`. Optional — without it the toast renders
|
|
19
|
+
* exactly as before. */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** Leading icon. A string renders as a glyph/emoji; a Snippet renders as-is.
|
|
22
|
+
* Ignored when `avatar` is set. */
|
|
23
|
+
icon?: Snippet | string;
|
|
24
|
+
/** Leading avatar image (takes precedence over `icon`). */
|
|
25
|
+
avatar?: ToastAvatar;
|
|
5
26
|
/** Extended detail text (e.g. a stack trace or error chain) — rendered by
|
|
6
27
|
* ToastHost behind a collapsible "Details" toggle. Multiline-safe. */
|
|
7
28
|
details?: string;
|
|
8
29
|
/** Arbitrary structured context carried on the Toast for programmatic
|
|
9
30
|
* access (getToasts()); not rendered by ToastHost. */
|
|
10
31
|
meta?: Record<string, unknown>;
|
|
32
|
+
/** Legacy single action. Still supported; folded into `actions`. */
|
|
11
33
|
action?: {
|
|
12
34
|
label: string;
|
|
13
35
|
onClick: () => void;
|
|
14
36
|
};
|
|
37
|
+
/** Several action buttons, rendered in order after the message. */
|
|
38
|
+
actions?: ToastAction[];
|
|
15
39
|
durationMs?: number;
|
|
16
40
|
onUndo?: () => void;
|
|
41
|
+
/** Show a draining countdown bar on a timed (non-undo) toast. It pauses on
|
|
42
|
+
* hover/focus together with the timer. Default false. */
|
|
43
|
+
showProgress?: boolean;
|
|
17
44
|
};
|
|
18
45
|
export type Toast = {
|
|
19
46
|
id: number;
|
|
20
47
|
type: ToastType;
|
|
21
48
|
message: string;
|
|
49
|
+
title?: string;
|
|
50
|
+
icon?: Snippet | string;
|
|
51
|
+
avatar?: ToastAvatar;
|
|
22
52
|
details?: string;
|
|
23
53
|
meta?: Record<string, unknown>;
|
|
24
54
|
action?: {
|
|
25
55
|
label: string;
|
|
26
56
|
onClick: () => void;
|
|
27
57
|
};
|
|
58
|
+
/** Normalised list: `actions` followed by the legacy `action` (if any). */
|
|
59
|
+
actions: ToastAction[];
|
|
28
60
|
durationMs: number;
|
|
29
61
|
onUndo?: () => void;
|
|
62
|
+
showProgress: boolean;
|
|
30
63
|
};
|
|
31
64
|
export type DismissHandle = {
|
|
32
65
|
dismiss: () => void;
|
|
33
66
|
};
|
|
34
67
|
export declare function getToasts(): Toast[];
|
|
68
|
+
/** Merge the legacy single `action` with `actions[]` (pure; exported for tests). */
|
|
69
|
+
export declare function normalizeToastActions(options: Pick<ToastOptions, 'action' | 'actions'>): ToastAction[];
|
|
35
70
|
export declare function showToast(options: ToastOptions): DismissHandle;
|
|
36
71
|
export declare function dismissToast(id: number): void;
|
package/dist/stores/toast.js
CHANGED
|
@@ -8,6 +8,15 @@ export function getToasts() {
|
|
|
8
8
|
return $.get(toasts);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/** Merge the legacy single `action` with `actions[]` (pure; exported for tests). */
|
|
12
|
+
export function normalizeToastActions(options) {
|
|
13
|
+
const list = [...options.actions ?? []];
|
|
14
|
+
|
|
15
|
+
if (options.action) list.push({ ...options.action, variant: 'ghost' });
|
|
16
|
+
|
|
17
|
+
return list;
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
export function showToast(options) {
|
|
12
21
|
const id = ++nextId;
|
|
13
22
|
|
|
@@ -15,11 +24,16 @@ export function showToast(options) {
|
|
|
15
24
|
id,
|
|
16
25
|
type: options.type ?? 'info',
|
|
17
26
|
message: options.message,
|
|
27
|
+
title: options.title,
|
|
28
|
+
icon: options.icon,
|
|
29
|
+
avatar: options.avatar,
|
|
18
30
|
details: options.details,
|
|
19
31
|
meta: options.meta,
|
|
20
32
|
action: options.action,
|
|
33
|
+
actions: normalizeToastActions(options),
|
|
21
34
|
durationMs: options.durationMs ?? 5000,
|
|
22
|
-
onUndo: options.onUndo
|
|
35
|
+
onUndo: options.onUndo,
|
|
36
|
+
showProgress: options.showProgress ?? false
|
|
23
37
|
};
|
|
24
38
|
|
|
25
39
|
$.set(toasts, [...$.get(toasts), toast], true);
|
package/dist/styles/motion.css
CHANGED
|
@@ -223,3 +223,75 @@
|
|
|
223
223
|
opacity: 1;
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
* One-shot utility animations (BoardUI wave 5, jatui-bns3w). All on the
|
|
229
|
+
* tokens above, so they scale with data-motion and collapse under reduced
|
|
230
|
+
* motion. Ideas after BoardUI's globals.css (MIT © 2026 Mertcan Dundar
|
|
231
|
+
* Esmergul).
|
|
232
|
+
*
|
|
233
|
+
* .jatui-check-draw — an SVG stroke with pathLength="1" draws itself on
|
|
234
|
+
* (the CheckGlyph tick carries its own copy).
|
|
235
|
+
* .jatui-row-exit — a list row slides aside and collapses its height;
|
|
236
|
+
* remove it on `animationend` (bulk-delete flows).
|
|
237
|
+
* .jatui-label-out / .jatui-label-in — a button label swap: old text
|
|
238
|
+
* lifts out, new text rises in.
|
|
239
|
+
*/
|
|
240
|
+
.jatui-check-draw {
|
|
241
|
+
stroke-dasharray: 1;
|
|
242
|
+
stroke-dashoffset: 1;
|
|
243
|
+
animation: jatui-check-draw var(--motion-fast) var(--motion-glide) forwards;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@keyframes jatui-check-draw {
|
|
247
|
+
to {
|
|
248
|
+
stroke-dashoffset: 0;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.jatui-row-exit {
|
|
253
|
+
overflow: hidden;
|
|
254
|
+
animation: jatui-row-exit var(--motion-move) var(--motion-standard) forwards;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@keyframes jatui-row-exit {
|
|
258
|
+
0% {
|
|
259
|
+
opacity: 1;
|
|
260
|
+
transform: translateX(0);
|
|
261
|
+
max-height: var(--jatui-row-h, 4rem);
|
|
262
|
+
}
|
|
263
|
+
50% {
|
|
264
|
+
opacity: 0;
|
|
265
|
+
transform: translateX(calc(var(--motion-travel) * 1.5));
|
|
266
|
+
max-height: var(--jatui-row-h, 4rem);
|
|
267
|
+
}
|
|
268
|
+
100% {
|
|
269
|
+
opacity: 0;
|
|
270
|
+
transform: translateX(calc(var(--motion-travel) * 1.5));
|
|
271
|
+
max-height: 0;
|
|
272
|
+
padding-block: 0;
|
|
273
|
+
margin-block: 0;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.jatui-label-out {
|
|
278
|
+
animation: jatui-label-out var(--motion-fast) var(--motion-glide) forwards;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.jatui-label-in {
|
|
282
|
+
animation: jatui-label-in var(--motion-enter) var(--motion-glide) both;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@keyframes jatui-label-out {
|
|
286
|
+
to {
|
|
287
|
+
opacity: 0;
|
|
288
|
+
transform: translateY(calc(var(--motion-travel) * -0.5));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@keyframes jatui-label-in {
|
|
293
|
+
from {
|
|
294
|
+
opacity: 0;
|
|
295
|
+
transform: translateY(calc(var(--motion-travel) * 0.5));
|
|
296
|
+
}
|
|
297
|
+
}
|
package/dist/styles/rail.css
CHANGED
|
@@ -74,3 +74,54 @@
|
|
|
74
74
|
line-height: 1rem; /* 16px */
|
|
75
75
|
letter-spacing: 0.01em;
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/* ─── Type ramp — composite text utilities (BoardUI wave 5) ──────────────
|
|
79
|
+
Each class sets size + line-height + tracking + weight together, so a
|
|
80
|
+
caption is one class instead of `text-xs font-medium tracking-wide`.
|
|
81
|
+
Deliberately small (six steps), not a Figma matrix. Colour is left to
|
|
82
|
+
the caller (text-base-content/60 etc.). Idea after BoardUI's
|
|
83
|
+
typography.css (MIT © 2026 Mertcan Dundar Esmergul).
|
|
84
|
+
─────────────────────────────────────────────────────────────────────── */
|
|
85
|
+
|
|
86
|
+
.text-jatui-caption {
|
|
87
|
+
font-size: 0.75rem; /* 12px */
|
|
88
|
+
line-height: 1rem;
|
|
89
|
+
letter-spacing: 0.01em;
|
|
90
|
+
font-weight: 400;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.text-jatui-label {
|
|
94
|
+
font-size: 0.6875rem; /* 11px — uppercase instrument label */
|
|
95
|
+
line-height: 1rem;
|
|
96
|
+
letter-spacing: 0.08em;
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
text-transform: uppercase;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.text-jatui-body {
|
|
102
|
+
font-size: 0.875rem; /* 14px */
|
|
103
|
+
line-height: 1.25rem;
|
|
104
|
+
letter-spacing: 0;
|
|
105
|
+
font-weight: 400;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.text-jatui-body-strong {
|
|
109
|
+
font-size: 0.875rem;
|
|
110
|
+
line-height: 1.25rem;
|
|
111
|
+
letter-spacing: 0;
|
|
112
|
+
font-weight: 600;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.text-jatui-title {
|
|
116
|
+
font-size: 1.125rem; /* 18px */
|
|
117
|
+
line-height: 1.5rem;
|
|
118
|
+
letter-spacing: -0.01em;
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.text-jatui-display {
|
|
123
|
+
font-size: 1.875rem; /* 30px */
|
|
124
|
+
line-height: 2.25rem;
|
|
125
|
+
letter-spacing: -0.02em;
|
|
126
|
+
font-weight: 700;
|
|
127
|
+
}
|